Showing preview only (762K chars total). Download the full file or copy to clipboard to get everything.
Repository: meetbill/op_practice_book
Branch: master
Commit: b055257d9744
Files: 48
Total size: 494.0 KB
Directory structure:
gitextract_bpfyvm88/
├── README.md
├── SUMMARY.md
├── doc/
│ ├── HA/
│ │ ├── README.md
│ │ ├── keepalived.md
│ │ ├── lb.md
│ │ └── lvs.md
│ ├── Linux/
│ │ ├── README.md
│ │ ├── base.md
│ │ ├── op.md
│ │ ├── optimize.md
│ │ ├── safety.md
│ │ ├── service.md
│ │ ├── shell.md
│ │ └── tools.md
│ ├── README.md
│ ├── cloud/
│ │ ├── README.md
│ │ ├── aliyun.md
│ │ ├── aws.md
│ │ ├── docker.md
│ │ ├── k8s.md
│ │ ├── kvm.md
│ │ ├── openstack.md
│ │ └── physical_machine.md
│ ├── cluster/
│ │ └── zookeeper.md
│ ├── db/
│ │ ├── README.md
│ │ ├── memcache.md
│ │ ├── mongodb.md
│ │ ├── mysql.md
│ │ └── redis.md
│ ├── monitor/
│ │ ├── README.md
│ │ ├── monit.md
│ │ └── zabbix.md
│ ├── other/
│ │ ├── README.md
│ │ └── windows.md
│ ├── store/
│ │ ├── RAID.md
│ │ ├── README.md
│ │ ├── ceph.md
│ │ ├── gfs.md
│ │ ├── glusterfs.md
│ │ ├── moosefs.md
│ │ └── store.md
│ └── web/
│ ├── README.md
│ ├── butterfly.md
│ ├── django.md
│ ├── nginx.md
│ └── web_base.md
├── run_web.sh
└── standard.md
================================================
FILE CONTENTS
================================================
================================================
FILE: README.md
================================================
# 运维实践指南
[](https://github.com/meetbill/op_practice_book/stargazers)
[](https://github.com/meetbill/op_practice_book/fork)
[](https://github.com/meetbill/op_practice_book/watchers)

## 阅读本书
> * [网上阅读(github)](https://github.com/meetbill/op_practice_book/blob/master/SUMMARY.md)
> * [下载本书(pdf)](https://www.gitbook.com/download/pdf/book/billwang139967/op_practice_book)
## 相关内容
> * [文档规范](./standard.md)
> * [wiki](https://github.com/meetbill/op_practice_book/wiki)
> * [相关程序下载](https://github.com/meetbill/op_practice_code)
> * [点击进行反馈](https://github.com/meetbill/op_practice_book/issues)
## 参加步骤
* 在 GitHub 上 `fork` 到自己的仓库,然后 `clone` 到本地,并设置用户信息。
```
$ git clone https://github.com/meetbill/op_practice_book.git
$ cd op_practice_book
$ git config user.name "yourname"
$ git config user.email "your email"
```
* 修改代码后提交,并推送到自己的仓库。
```
$ #do some change on the content
$ git commit -am "Fix issue #1: change helo to hello"
$ git push
```
* 在 GitHub 网站上提交 pull request。
* 定期使用项目仓库内容更新自己仓库内容。
```
$ git remote add upstream https://github.com/meetbill/op_practice_book.git
$ git fetch upstream
$ git checkout master
$ git rebase upstream/master
$ git push -f origin master
```
## 小额捐款
如果觉得 `op_practice_book` 对您有帮助,可以请笔者喝杯咖啡(支付宝)

## Stargazers over time
[](https://starchart.cc/meetbill/op_practice_book)
================================================
FILE: SUMMARY.md
================================================
# Summary
* [前言](doc/README.md)
* [Linux 篇](doc/Linux/README.md)
* [Linux 基础](doc/Linux/base.md)
* [Linux 工具](doc/Linux/tools.md)
* [Linux 安全](doc/Linux/safety.md)
* [Linux 优化](doc/Linux/optimize.md)
* [脚本编程 (shell)](doc/Linux/shell.md)
* [常见服务架设](doc/Linux/service.md)
* [常用问题处理](doc/Linux/op.md)
* [数据库及缓存篇](doc/db/README.md)
* [MySQL](doc/db/mysql.md)
* [MongoDB](doc/db/mongodb.md)
* [Redis](doc/db/redis.md)
* [MemCache](doc/db/memcache.md)
* [Web 篇](doc/web/README.md)
* [Web 基础](doc/web/web_base.md)
* [Nginx](doc/web/nginx.md)
* [Django](doc/web/django.md)
* [Butterfly](doc/web/butterfly.md)
* [监控篇](doc/monitor/README.md)
* [Zabbix](doc/monitor/zabbix.md)
* [Monit](doc/monitor/monit.md)
* [存储篇](doc/store/README.md)
* [磁盘及 RAID](doc/store/RAID.md)
* [DAS/SAN/NAS](doc/store/store.md)
* [GFS](doc/store/gfs.md)
* [GlusterFS](doc/store/glusterfs.md)
* [Ceph](doc/store/ceph.md)
* [MooseFS](doc/store/moosefs.md)
* [物理机,云服务及虚拟化篇](doc/cloud/README.md)
* [物理机](doc/cloud/physical_machine.md)
* [AWS](doc/cloud/aws.md)
* [阿里云](doc/cloud/aliyun.md)
* [KVM](doc/cloud/kvm.md)
* [Docker](doc/cloud/docker.md)
* [OpenStack](doc/cloud/openstack.md)
* [K8s](doc/cloud/k8s.md)
* [集群应用篇](doc/HA/README.md)
* [负载均衡](doc/HA/lb.md)
* [LVS](doc/HA/lvs.md)
* [高可用的 LVS 负载均衡集群](doc/HA/keepalived.md)
* [ZooKeeper](./doc/cluster/zookeeper.md)
* [其他篇](doc/other/README.md)
* [Windows 下服务](doc/other/windows.md)
================================================
FILE: doc/HA/README.md
================================================
# 集群应用篇
> * 负载均衡
> * LVS
> * 高可用的 LVS 负载均衡集群
================================================
FILE: doc/HA/keepalived.md
================================================
## Keepalived 使用
<!-- vim-markdown-toc GFM -->
* [1 Keepalived 介绍及安装](#1-keepalived-介绍及安装)
* [1.1 介绍](#11-介绍)
* [1.1.1 LVS 和 Keepalived 的关系](#111-lvs-和-keepalived-的关系)
* [1.2 安装](#12-安装)
* [1.3 使用](#13-使用)
* [2 Keepalived 配置相关](#2-keepalived-配置相关)
* [2.1 global defs 区域](#21-global-defs-区域)
* [2.2 vrrp script 区域](#22-vrrp-script-区域)
* [2.3 VRRPD 配置](#23-vrrpd-配置)
* [2.3.1 VRRP Sync Groups](#231-vrrp-sync-groups)
* [2.3.2 VRRP 实例配置](#232-vrrp-实例配置)
* [2.4 LVS 配置](#24-lvs-配置)
* [3 Keepalived 工作原理](#3-keepalived-工作原理)
* [3.1 VRRP 工作流程](#31-vrrp-工作流程)
* [3.2 MASTER 和 BACKUP 节点的优先级如何调整?](#32-master-和-backup-节点的优先级如何调整)
* [3.3 ARP 查询处理](#33-arp-查询处理)
* [3.4 虚拟 IP 地址和 MAC 地址](#34-虚拟-ip-地址和-mac-地址)
* [3.5 Keepalived 进程](#35--keepalived-进程)
* [3.6 Keepalived 健康检查方式](#36-keepalived-健康检查方式)
* [4 Keepalived 场景应用](#4-keepalived-场景应用)
* [4.1 Keepalived 主从切换](#41-keepalived-主从切换)
* [4.2 Keepalived 仅做 HA 时的配置](#42-keepalived-仅做-ha-时的配置)
* [5 其他配置](#5-其他配置)
* [5.1 重定向 Keepalived 输出日志](#51-重定向-keepalived-输出日志)
* [5.2 只用 VRRP 模块](#52-只用-vrrp-模块)
* [6 常见问题](#6-常见问题)
* [6.1 virtual_router_id 冲突](#61-virtual_router_id-冲突)
* [6.2 VIP 无法访问](#62-vip-无法访问)
* [6.2.1 VIP 被抢占](#621-vip-被抢占)
* [6.2.2 网关的 ARP 缓存没有刷新](#622-网关的-arp-缓存没有刷新)
<!-- vim-markdown-toc -->
# 1 Keepalived 介绍及安装
## 1.1 介绍
Keepalived 是一个基于 VRRP 协议来实现的 WEB 服务高可用方案,其功能类似于 [heartbeat], 可以利用其来避免单点故障。一个 WEB 服务至少会有 2 台服务器运行 Keepalived,一台为主服务器(MASTER),一台为备份服务器(BACKUP),但是对外表现为一个虚拟 IP,主服务器会发送特定的消息给备份服务器,当备份服务器收不到这个消息的时候,即主服务器宕机的时候,备份服务器就会接管虚拟 IP,继续提供服务,从而保证了高可用性。
+---------VIP(192.168.0.3)----------+
| |
| |
server(MASTER) <----keepalived----> server(BACKUP)
(192.168.0.1) (192.168.0.2)
### 1.1.1 LVS 和 Keepalived 的关系
LVS 可以不依赖 Keepalived 而进行分发请求,但是想让负载调度器动态监控真实服务器心跳 需要写很复杂的代码。而 Keepalived 正是一个通过简单配置就能满足请求分发、心跳检测、集群管理的好工具
## 1.2 安装
编译安装:
$ wget http://www.keepalived.org/software/keepalived-1.2.2.tar.gz</a>
$ tar -zxvf keepalived-1.2.2.tar.gz
$ cd keepalived-1.2.2
$ ./configure --prefix=/usr/local/keepalived
$ make && make install
拷贝需要的文件:
$ cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/keepalived
$ cp /usr/local/keepalived/sbin/keepalived /usr/sbin/
$ cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
$ mkdir -p /etc/keepalived/
$ cp /usr/local/etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf
`/etc/keepalived/keepalived.conf`是默认配置文件
## 1.3 使用
$ /etc/init.d/keepalived start | restart | stop
当启动了 Keepalived 之后,通过`ifconfig`是看不到 VIP 的,但是通过`ip a`命令是可以看到的。 当 MASTER 宕机,BACKUP 升级为 MASTER,这些 VRRP_Instance 状态的切换都可以在`/var/log/message`中进行记录。
# 2 Keepalived 配置相关
Keepalived 只有一个配置文件 /etc/keepalived/keepalived.conf,里面主要包括以下几个配置区域,分别是 global\_defs、static\_ipaddress、static\_routes、vrrp_script、vrrp\_instance 和 virtual\_server。
## 2.1 global defs 区域
主要是配置故障发生时的通知对象以及机器标识
```
global_de_s {
notification_email {
a@abc.com
b@abc.com
...
}
notification_email_from alert@abc.com
smtp_server smtp.abc.com
smtp_connect_timeout 30
enable_traps
router_id host163
}
```
* notification_email 故障发生时给谁发邮件通知。
* notification_email_from 通知邮件从哪个地址发出。
* smpt_server 通知邮件的 smtp 地址。
* smtp_connect_timeout 连接 smtp 服务器的超时时间。
* enable_traps 开启 SNMP 陷阱([Simple Network Management Protocol][snmp])。
* router_id 标识本节点的字条串,通常为 hostname,但不一定非得是 hostname。故障发生时,邮件通知会用到。
## 2.2 vrrp script 区域
用来做健康检查的,当时检查失败时会将`vrrp_instance`的`priority`减少相应的值。
```
vrrp_script chk_http_port {
script "</dev/tcp/127.0.0.1/80"
interval 1
weight -10
}
```
以上意思是如果`script`中的指令执行失败,那么相应的`vrrp_instance`的优先级会减少 10 个点。
## 2.3 VRRPD 配置
在 [VRRP] 协议中,有两组重要的概念:
> * VRRP 路由器和虚拟路由器
> * 主控路由器和备份路由器
VRRP 路由器是指运行 VRRP 的路由器,是物理实体,虚拟路由器是指 VRRP 协议创建的,是逻辑概念。一组 VRRP 路由器协同工作,共同构成一台虚拟路由器。该虚拟路由器对外表现为一个具有唯一固定 IP 地址和 MAC 地址的逻辑路由器。处于同一个 VRRP 组中的路由器具有两种互斥的角色:
主控路由器和备份路由器,一个 VRRP 组中有且只有一台处于主控角色的路由器,可以有一个或者多个处于备份角色的路由器。VRRP 协议使用选择策略从路由器组中选出一台作为主控,负责 ARP 相应和转发 IP 数据包,组中的其它路由器作为备份的角色处于待命状态。当由于某种原因主控路由器发生故障时,备份路由器能在几秒钟的时延后升级为主路由器。由于此切换非常迅速而且不用改变 IP 地址和 MAC 地址,故对终端使用者系统是透明的。
VRRPD 配置包括两部分
> * VRRP 同步组 (synchroization group)
> * VRRP 实例 (VRRP Instance)
### 2.3.1 VRRP Sync Groups
vrrp_rsync_group 用来定义 vrrp_intance 组,使得这个组内成员动作一致。举个例子来说明一下其功能:
两个 vrrp_instance 同属于一个 vrrp_rsync_group,那么其中一个 vrrp_instance 发生故障切换时,另一个 vrrp_instance 也会跟着切换(即使这个 instance 没有发生故障)。
eg: 机器有两个网段,一个内网一个外网,每个网段开启一个 VRRP 实例,假设 VRRP 配置为检查内网,那么当外网出现问题时,VRRPD 认为自己仍然健康,那么不会发送 Master 和 Backup 的切换,从而导致了问题。Sync group 就是为了解决这个问题。可以将两个实例都放到一个 Sync group,这样,group 里面任何一个实例出现问题都会发生切换
```
vrrp_sync_group VG_1 {
group {
inside_network # name of vrrp_instance (below)
outside_network # One for each moveable IP.
...
}
notify_master /path/to_master.sh
notify_backup /path/to_backup.sh
notify_fault "/path/fault.sh VG_1"
notify /path/notify.sh
smtp_alert
}
```
* notify_master/backup/fault 分别表示切换为主 / 备 / 出错时所执行的脚本。
* notify 表示任何一状态切换时都会调用该脚本,并且该脚本在以上三个脚本执行完成之后进行调用,Keepalived 会自动传递三个参数($1 = "GROUP"|"INSTANCE",$2 = name of group or instance,$3 = target state of transition(MASTER/BACKUP/FAULT))。
* smtp_alert 表示是否开启邮件通知(用全局区域的邮件设置来发通知)。
### 2.3.2 VRRP 实例配置
vrrp_instance 用来定义对外提供服务的 VIP 区域及其相关属性。
```
vrrp_instance VI_1 {
state MASTER
interface eth0
use_vmac <VMAC_INTERFACE>
dont_track_primary
track_interface {
eth0
eth1
}
mcast_src_ip <IPADDR>
lvs_sync_daemon_interface eth1
garp_master_delay 10
virtual_router_id 1
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass 12345678
}
virtual_ipaddress {
10.210.214.253/24 brd 10.210.214.255 dev eth0
192.168.1.11/24 brd 192.168.1.255 dev eth1
}
virtual_routes {
172.16.0.0/12 via 10.210.214.1
192.168.1.0/24 via 192.168.1.1 dev eth1
default via 202.102.152.1
}
track_script {
chk_http_port
}
nopreempt
preempt_delay 300
debug
notify_master <STRING>|<QUOTED-STRING>
notify_backup <STRING>|<QUOTED-STRING>
notify_fault <STRING>|<QUOTED-STRING>
notify <STRING>|<QUOTED-STRING>
smtp_alert
}
```
* state 可以是 MASTER 或 BACKUP,不过当其他节点 Keepalived 启动时会将 priority 比较大的节点选举为 MASTER,因此该项其实没有实质用途。
* interface 节点固有 IP(非 VIP)的网卡,用来发 VRRP 包。
* use_vmac 是否使用 VRRP 的虚拟 MAC 地址。
* dont_track_primary 忽略 VRRP 网卡错误。(默认未设置)
* track_interface 监控以下网卡,如果任何一个不通就会切换到 FALT 状态。(可选项)
* mcast_src_ip 修改 vrrp 组播包的源地址,默认源地址为 master 的 IP。(由于是组播,因此即使修改了源地址,该 master 还是能收到回应的)
* lvs_sync_daemon_interface 绑定 lvs syncd 的网卡。
* garp_master_delay 当切为主状态后多久更新 ARP 缓存,默认 5 秒。
* virtual_router_id 取值在 0-255 之间,用来区分多个 instance 的 VRRP 组播。
注意: 同一网段中 virtual_router_id 的值不能重复,否则会出错,相关错误信息如下。
```
Keepalived_vrrp[27120]: ip address associated with VRID not present in received packet :
one or more VIP associated with VRID mismatch actual MASTER advert
bogus VRRP packet received on eth1 !!!
receive an invalid ip number count associated with VRID!
VRRP_Instance(xxx) ignoring received advertisment...
```
可以用这条命令来查看该网络中所存在的 vrid:`tcpdump -nn -i any net 224.0.0.0/8`
* priority 用来选举 master 的,要成为 master,那么这个选项的值 [最好高于其他机器 50 个点][priority_more_than_50],该项 [取值范围][priority] 是 1-255(在此范围之外会被识别成默认值 100)。
* advert_int 发 VRRP 包的时间间隔,即多久进行一次 master 选举(可以认为是健康查检时间间隔)。
* authentication 认证区域,认证类型有 PASS 和 HA(IPSEC),推荐使用 PASS(密码只识别前 8 位)。
* virtual_ipaddress VIP,不解释了。
* virtual_routes 虚拟路由,当 IP 漂过来之后需要添加的路由信息。
* virtual_ipaddress_excluded 发送的 VRRP 包里不包含的 IP 地址,为减少回应 VRRP 包的个数。在网卡上绑定的 IP 地址比较多的时候用。
* nopreempt 允许一个 priority 比较低的节点作为 master,即使有 priority 更高的节点启动。
首先 nopreemt 必须在 state 为 BACKUP 的节点上才生效(因为是 BACKUP 节点决定是否来成为 MASTER 的),其次要实现类似于关闭 auto failback 的功能需要将所有节点的 state 都设置为 BACKUP,或者将 master 节点的 priority 设置的比 BACKUP 低。我个人推荐使用将所有节点的 state 都设置成 BACKUP 并且都加上 nopreempt 选项,这样就完成了关于 autofailback 功能,当想手动将某节点切换为 MASTER 时只需去掉该节点的 nopreempt 选项并且将 priority 改的比其他节点大,然后重新加载配置文件即可(等 MASTER 切过来之后再将配置文件改回去再 reload 一下)。
当使用`track_script`时可以不用加`nopreempt`,只需要加上`preempt_delay 5`,这里的间隔时间要大于`vrrp_script`中定义的时长。
* preempt_delay master 启动多久之后进行接管资源(VIP/Route 信息等),并提是没有`nopreempt`选项。
## 2.4 LVS 配置
virtual_server_group 一般在超大型的 LVS 中用到,一般 LVS 用不过这东西,因此不多说。
```
virtual_server IP Port {
delay_loop <INT>
lb_algo rr|wrr|lc|wlc|lblc|sh|dh
lb_kind NAT|DR|TUN
persistence_timeout <INT>
persistence_granularity <NETMASK>
protocol TCP
ha_suspend
virtualhost <STRING>
alpha
omega
quorum <INT>
hysteresis <INT>
quorum_up <STRING>|<QUOTED-STRING>
quorum_down <STRING>|<QUOTED-STRING>
sorry_server <IPADDR> <PORT>
real_server <IPADDR> <PORT> {
weight <INT>
inhibit_on_failure
notify_up <STRING>|<QUOTED-STRING>
notify_down <STRING>|<QUOTED-STRING>
# HTTP_GET|SSL_GET|TCP_CHECK|SMTP_CHECK|MISC_CHECK
HTTP_GET|SSL_GET {
url {
path <STRING>
# Digest computed with genhash
digest <STRING>
status_code <INT>
}
connect_port <PORT>
connect_timeout <INT>
nb_get_retry <INT>
delay_before_retry <INT>
}
}
}
```
* delay_loop 延迟轮询时间(单位秒)。
* lb_algo 后端调试算法(load balancing algorithm)。
* lb_kind LVS 调度类型 [NAT][nat]/[DR][dr]/[TUN][tun]。
* virtualhost 用来给 HTTP_GET 和 SSL_GET 配置请求 header 的。
* sorry_server 当所有 real server 宕掉时,sorry server 顶替。
* real_server 真正提供服务的服务器。
* weight 权重。
* notify_up/down 当 real server 宕掉或启动时执行的脚本。
* 健康检查的方式,N 多种方式。
* path 请求 real serserver 上的路径。
* digest/status_code 分别表示用 genhash 算出的结果和 http 状态码。
* connect_port 健康检查,如果端口通则认为服务器正常。
* connect_timeout,nb_get_retry,delay_before_retry 分别表示超时时长、重试次数,下次重试的时间延迟。
其他选项暂时不作说明。
# 3 Keepalived 工作原理
Keepalived 是以 VRRP 协议为实现基础的,VRRP 全称 Virtual Router Redundancy Protocol,即***虚拟路由冗余协议***。
虚拟路由冗余协议,可以认为是实现路由器高可用的协议,即将 N 台提供相同功能的路由器组成一个路由器组,这个组里面有一个 master 和多个 backup,master 上面有一个对外提供服务的 VIP(该路由器所在局域网内其他机器的默认路由为该 VIP),master 会发组播,当 backup 收不到 vrrp 包时就认为 master 宕掉了,这时就需要根据 VRRP 的优先级***vrrp_priority***来选举一个 backup 当 master***select_master***。这样的话就可以保证路由器的高可用了。
Keepalived 主要有三个模块,分别是 core、check 和 vrrp。core 模块为 Keepalived 的核心,负责主进程的启动、维护以及全局配置文件的加载和解析。check 负责健康检查,包括常见的各种检查方式。vrrp 模块是来实现 VRRP 协议的。
```
+-------------+
| uplink |
+-------------+
|
+
keep|alived
192.168.0.3
+-------------+
| virtualIP |
+-------------+
192.168.0.1 主 | 192.168.0.2
+--------------+ | +--------------+
|LVS+Keepalived|---|LVS+Keepalived|
+--------------+ +--------------+
+------------------+------------------+
| | |
+-------------+ +-------------+ +-------------+
| web01 | | web02 | | web03 |
+-------------+ +-------------+ +-------------+
```
## 3.1 VRRP 工作流程
(1). 初始化
路由器启动时,如果路由器的优先级是 255(最高优先级,路由器拥有路由器地址), 要发送 VRRP 通告信息,并发送广播 ARP 信息通告路由器 IP 地址
对应的 MAC 地址为路由虚拟 MAC, 设置通告信息定时器准备定时发送 VRRP 通告信息,转为 MASTER 状态;否则进入 BACKUP 状态,设置定时器检查
定时检查是否收到 MASTER 的通告信息。
(2).Master
设置定时通告定时器;
用 VRRP 虚拟 MAC 地址响应路由器 IP 地址的 ARP 请求;
转发目的 MAC 是 VRRP 虚拟 MAC 的数据包;
如果是虚拟路由器 IP 的拥有者,将接受目的地址是虚拟路由器 IP 的数据包,否则丢弃;
当收到 shutdown 的事件时删除定时通告定时器,发送优先权级为 0 的通告包,转初始化状态;
如果定时通告定时器超时时,发送 VRRP 通告信息;
收到 VRRP 通告信息时,如果优先权为 0, 发送 VRRP 通告信息;否则判断数据的优先级是否高于本机,或相等而且实际 IP 地址大于本地实际 IP, 设置定时通告定时器,复位主机超时定时器,转 BACKUP 状态;否则的话,丢弃该通告包;
(3).Backup
设置主机超时定时器;
不能响应针对虚拟路由器 IP 的 ARP 请求信息;
丢弃所有目的 MAC 地址是虚拟路由器 MAC 地址的数据包;
不接受目的是虚拟路由器 IP 的所有数据包;
当收到 shutdown 的事件时删除主机超时定时器,转初始化状态;
主机超时定时器超时的时候,发送 VRRP 通告信息,广播 ARP 地址信息,转 MASTER 状态;
收到 VRRP 通告信息时,如果优先权为 0, 表示进入 MASTER 选举;否则判断数据的优先级是否高于本机,如果高的话承认 MASTER 有效,复位主机超时定时器;否则的话,丢弃该通告包;
## 3.2 MASTER 和 BACKUP 节点的优先级如何调整?
首先,每个节点有一个初始优先级,由配置文件中的 priority 配置项指定,MASTER 节点的 priority 应比 BAKCUP 高。
运行过程中 Keepalived 根据 vrrp_script 的 weight 设定,增加或减小节点优先级。规则如下:
1. 当 weight > 0 时,vrrp_script script 脚本执行返回 0(成功)时优先级为 priority + weight, 否则为 priority.
当 BACKUP 发现自己的优先级大于 MASTER 通告的优先级时,进行主从切换。
2. 当 weight < 0 时,vrrp_script script 脚本执行返回非 0(失败)时优先级为 priority + weight, 否则为 priority.
当 BACKUP 发现自己的优先级大于 MASTER 通告的优先级时,进行主从切换。
3. 当两个节点的优先级相同时,以节点发送 VRRP 通告的 IP 作为比较对象,IP 较大者为 MASTER.
以上文中的配置为例:
HOST1: 192.168.0.1, priority=91, MASTER(default)
HOST2: 192.168.0.2, priority=90, BACKUP
VIP: 192.168.0.3 weight = 2
抓包命令:tcpdump -nn vrrp
## 3.3 ARP 查询处理
当内部主机通过 ARP 查询虚拟路由器 IP 地址对应的 MAC 地址时,MASTER 路由器回复的 MAC 地址为虚拟的 VRRP 的 MAC 地址,而不是实际网卡的
MAC 地址,这样在路由器切换时让内网机器觉察不到;而在路由器重新启动时,不能主动发送本机网卡的实际 MAC 地址。如果虚拟路由器开启的 ARP
代理 (proxy_arp) 功能,代理的 ARP 回应也回应 VRRP 虚拟 MAC 地址;
## 3.4 虚拟 IP 地址和 MAC 地址
VRRP 组(备份组)中的虚拟路由器对外表现为唯一的虚拟 MAC 地址,地址格式为 00-00-5E-00-01-[VRID], VRID 为 VRRP 组的编号,范围是 0~255.
## 3.5 Keepalived 进程
Keepalived 主要有三个模块,分别是 core、check 和 vrrp。
> * core 模块为 Keepalived 的核心,负责主进程的启动、维护以及全局配置文件的加载和解析。
> * check 负责健康检查,包括常见的各种检查方式。
> * vrrp 模块是来实现 VRRP 协议的。
## 3.6 Keepalived 健康检查方式
Keepalived 对后端 realserver 的健康检查方式主要有以下几种:
***TCP_CHECK***
工作在第 4 层,Keepalived 向后端服务器发起一个 tcp 连接请求,如果后端服务器没有响应或者超时,那么这个后端将从服务器中移除
***HTTP_GET***
工作在第 5 层,通过向指定的 URL 执行 http 请求,将得到的结果比对(经检验此种方法在多个实体服务器只能检测到第一个,故不可行)
***SSL_GET***
与 HTTP_GET 类似
***MISC_CHECK***
用脚本来检测,脚本如果带有参数,需要将脚本和参数放入到双引号内。脚本的返回值需要为:
> * 0-------------- 检测成功
> * 1-------------- 检测失败,将从服务器池中移除
> * 2~255-------- 检测成功;如果有设置 misc_dynamic,权重自动调整为退出码 -2,如果退出码为 200,权重自动调整为 198=200-2
# 4 Keepalived 场景应用
## 4.1 Keepalived 主从切换
主从切换比较让人蛋疼,需要将 backup 配置文件的 priority 选项的值调整的比 master 高 50 个点,然后 reload 配置文件就可以切换了。当时你也可以将 master 的 Keepalived 停止,这样也可以进行主从切换。
## 4.2 Keepalived 仅做 HA 时的配置
请看该文档同级目录下的配置文件示例。
用 tcpdump 命令来捕获的结果如下:
```
17:20:07.919419 IP 192.168.1.1 > 224.0.0.18: VRRPv2, Advertisement, vrid 1, prio 200, authtype simple, intvl 1s, length 20
```
# 5 其他配置
## 5.1 重定向 Keepalived 输出日志
(1) 修改 /etc/sysconfig/keepalived
把 KEEPALIVED_OPTIONS="-D" 修改为 KEEPALIVED_OPTIONS="-D -d -S 0"
其中 -S 指定 syslog 的 facility"
同时创建 /var/log/keepalived 目录
```
#mkdir /var/log/keepalived
```
(2) 在 /etc/rsyslog.conf 中添加
```
# keepalived -S 0
local0.* /var/log/keepalived/keepalived.log
```
(3) 重启 Rsyslog 和 Keepalived 服务
```
#/etc/init.d/rsyslog restart
#/etc/init.d/Keepalived restart
```
## 5.2 只用 VRRP 模块
假如不使用 LVS 的话,即无需加载 ip_vs 模块(注;不装 ipvsadm 的话,直接启动 Keepalived 的话,会因为没有 ip_vs 模块而一直在日志中输出错误日志)
修改 /etc/sysconfig/keepalived
把 KEEPALIVED_OPTIONS="-D" 修改为 KEEPALIVED_OPTIONS="-D -P"
# 6 常见问题
## 6.1 virtual_router_id 冲突
Keepalived 日志提示
```
[Time] [Hostname] Keepalived_vrrp: ip address associated with VRID not present in received packet : [VIP]
[Time] [Hostname] Keepalived_vrrp: one or more VIP associated with VRID mismatch actual MASTER advert
[Time] [Hostname] Keepalived_vrrp: bogus VRRP packet received on eth0 !!!
[Time] [Hostname] Keepalived_vrrp: VRRP_Instance(web_1) Dropping received VRRP packet...
```
解决方法
```
同一集群的 Keepalived 的主、备机的 virtual_router_id 必须相同,取值 0-255
但是同一内网中不应有相同 virtual_router_id 的集群
修改 virtual_router_id 就可以了
```
## 6.2 VIP 无法访问
### 6.2.1 VIP 被抢占
```
arping -I eth0 VIP
```
查看是否输出两个不同的 Mac 地址,如果是则地址被占用
### 6.2.2 网关的 ARP 缓存没有刷新
```
arping -I eth0 -c 5 -s VIP GATEWAY
```
================================================
FILE: doc/HA/lb.md
================================================
## 负载均衡
<!-- vim-markdown-toc GFM -->
* [解决的问题](#解决的问题)
* [网络层次上的负载均衡](#网络层次上的负载均衡)
* [四层负载均衡](#四层负载均衡)
* [七层负载均衡](#七层负载均衡)
* [负载均衡算法](#负载均衡算法)
<!-- vim-markdown-toc -->
## 解决的问题
* 能够将大规模并发访问和数据流量分发到多台内部服务器上,减少用户的等待时间;
* 当有重负载的计算请求时,能够将请求分解成多个任务,并将这些任务分配到内部多个计算服务器上,收集处理内部计算服务器的处理结果,汇总结果并返回给用户;
* 负载均衡能够大大提高系统的处理能力、提高系统灵活性;
* 高可用:当某服务器出现故障时,不影响其它服务器和用户的运行和使用;
* 当后端某个服务器出现故障时,能够将该服务器从服务列表中删除,当服务器恢复时,再将该服务器加入到列表中;
* 可伸缩:能够不影响其它服务器和用户的情况下进行扩容;
负载均衡的本质是数据包的转发,即如何将数据包转发到负载最小的服务器上去。最常用的硬件方案有 F5,软件方案有 LVS+Keepalived。
## 网络层次上的负载均衡
* 二层负载均衡,是通过一个虚拟的 MAC 地址接收请求,然后再分配到真实的 MAC 地址;
* 三层负载均衡,是通过一个虚拟的 IP 地址接收请求,然后分配到真实的 IP 地址;
* 四层负载均衡,是通过一个虚拟 IP+ 端口进行接收,然后分配到真实的服务器;
* 七层负载均衡,是通过一个虚拟的主机名或 URL 接收请求,然后分配到真实的服务器。可以根据 URL,浏览器类别,语言等,将请求发给不同的内部服务器。
### 四层负载均衡
* 首先会配置 frontend 的 IP:PORT 与 backend 的 IP:PORT 映射关系。当有客户端请求到来时,会根据映射关系,将请求转发到 backend 的服务器上去。
* 工作在 L4 层的负载均衡器,不需要对客户端的数据包内容进行解析。如 SYN 包到来时,负载均衡器只需要选择一个最佳的内部服务器,将 SYN 包中的 dst IP:PORT 替换为内部服务器的 IP:PORT,并直接转发给该内部服务器即可。对有些部署,可能还需要修改 source IP:PORT,这样负载均衡器可以收到内部服务器返回的包。

### 七层负载均衡
L7 层负载均衡,是应用层的负载均衡。
负载均衡器需要先和客户端建立连接 (TCP 三次握手),接收客户端发过来的报文,然后根据报文特定字段的内容,来选择内部服务器。L7 层负载均衡器,是一个代理服务器,需要与客户端和内部服务器间都建立连接。
一般来说,L7 层负载均衡的处理能力,低于 L4 层。

优点:
- 能够更好的拓展内部网络。如:能够将使用英语的和使用汉语的客户端请求,发送到不同的内部服务器。
- 能够将对图片的请求,发送到图片服务器,同时图片服务器可以加缓存。
- 能够提前过滤掉一些非法的请求和无用的数据包,而不用将这些请求发送到内部服务器,减轻内部服务器的压力。
缺点:
- 速度上,不如 L4 层快
## 负载均衡算法
- 轮循(Round Robin):将每次请求,轮流的分配给内部服务器。当内部服务器的软硬件配置相当时,比较适合。
- 权重轮循(Weighted Round Robin):根据内部服务器的配置不同,给每台服务器一个权重。如服务器 A 的权值被设计成 1,B 的权值是 3,C 的权值是 2,客户请求依次发给 [ABBBCC]。权重大的,分配到的任务就多。
- 随机(Random):将请求随机分配给内部中的多个服务器。
- 权重随机(Weighted Random):此种均衡算法类似于权重轮循算法,不过在处理请求分担时是个随机选择的过程。
- 响应速度(Response Time):负载均衡器与内部服务器建立连接,并定时向内部服务器发 ping 包(或者其他包也行)。根据各服务器的响应速度,决定将用户请求发给哪台内部服务器。该均衡算法能够较好的反应内部服务器运行状况。
- 连接数(Connections):有些内部服务器可能直接与客户端建立连接。这时可以询问内部服务器当前的用户连接数,并将新的用户请求发送给连接数最少的内部服务器。比较适合长连接。
- 处理能力(Processing Capacity):将内部服务器的 CPU/ 内存 /IO/ 当前负载,根据一定的算法换成负载值,并定时上报给负载均衡器。负载均衡器每次将用户请求转发给当前 Load 值最低的内部服务器。
- DNS 轮询:DNS 也可以用来做负载均衡。网络上,客户端一般通过域名来找到服务器的 IP 地址,DNS 服务器在接收客户端查询时,按顺序将服务器的 IP 地址返回给客户端,来达到均衡的目的。比较适合全局负载均衡。
- Hash:将访问用户的 IP 地址进行 hash,根据 hash 的结果来决定将该用户定向到哪台后端服务器。
================================================
FILE: doc/HA/lvs.md
================================================
## LVS
<!-- vim-markdown-toc GFM -->
* [LVS 简介](#lvs-简介)
* [数据包三种转发方式](#数据包三种转发方式)
* [NAT 网络地址翻译技术](#nat-网络地址翻译技术)
* [TUN IP 隧道技术](#tun-ip-隧道技术)
* [DR 直接转发](#dr-直接转发)
* [配置脚本](#配置脚本)
* [ipvsadm](#ipvsadm)
* [tcpdump 抓包分析](#tcpdump-抓包分析)
* [问题分析](#问题分析)
<!-- vim-markdown-toc -->
## LVS 简介
[LVS](http://www.linuxvirtualserver.org/)(Linux Virtual Server),早已加入到 Linux 内核中。
LVS 使用的是 IP 负载均衡技术 (ipvs 模块实现)。LVS 安装在 DirectorServer(DS) 上,DS 根据配置信息虚拟出一个 ip(VIP),同时根据配置信息,生成路由表,将用户的数据包按照一定的法则转发到后端 RealServer(RS) 上。
LVS 进行 L4 层转发,且在内核中,速度极快。与 Keepalived 相比,缺少对内部服务器的健康检查,且存在单点故障。
LVS 使用 ipvsadm 来配置 ipvs。
数据包的转发,有三种方法:NAT/TUN/DR。
| 模式 | 网络要求 | 是否需要 VIP | 端口映射 | DS 参与回包 | ARP 隔离 | 效率 |
| ---- | ----------------------- | ----------- | -------- | ---------- | ------- | ---- |
| NAT | 同一网段 | 不需要 | 支持 | 是 | 不需要 | 最慢 |
| TUN | 可在同一物理网络或不同物理网络 | 需要 | - | 否 | - | 中等 |
| DR | 同一物理网络 | 需要 | 不支持 | 否 | 需要 | 最高 |
## 数据包三种转发方式
### NAT 网络地址翻译技术

DS 和 RS,都在同一个网段,才能进行 NAT 模式转发。同时需要将 DS 的内部 IP 设置为内部网络的默认网关,RS 在回包时,直接发给内部网关(即 DS),由内部网关进行转发。
用户通过 DS 的外部 IP 地址进行访问。
NAT 模式下,是可以进行端口映射的。
整个数据包流程如下:
```sh
假设用户 IP 为 10.15.62.204,使用端口 6356;
用户 ->DS: src(10.15.62.204:6356) dst(10.15.144.71:80)
DS 的外部 IP 收到数据包后,内核进行转发
DS->RS2: src(10.15.62.204:6356) dst(192.168.1.12:10012)
RS2 收到数据包,处理完毕后,将回包通过内部网关发出去
RS2->DS: src(192.168.1.12:10012) dst(10.15.62.204:6356)
DS 收到会包后,由内核转发给用户
DS->用户:src(10.15.144.71:80) dst(10.15.62.204:6356)
```
### TUN IP 隧道技术

调度器采用 IP 隧道技术,将用户的请求转发到 RS,RS 直接将响应发给用户。
TUN 模式下,RS 的回包,不需要经过 DS,而是直接发给客户端。
### DR 直接转发

DS 通过改写请求报文的 MAC 地址,将请求发给 RS,RS 直接将响应发给用户。
**DR 方式的效率最高**,但要求 DS 和 RS 在同一物理网络。
DR 模式不支持端口映射;同时 RS 需要抑制关于 VIP 的 ARP 应答。
整个数据包处理过程:
```sh
假设客户端 IP 10.15.62.204,使用端口 6356
用户 ->DS: src(10.15.62.204:63565) dst(10.15.144.120:80)
DS 收到包后,通过负载均衡算法,选择 RS2,改写包的目的 MAC 地址,将数据包发给 RS2
DS->RS2: src(10.15.62.204:63565) dst(10.15.144.120:80),目的 MAC 发生改变
RS2 处理完毕后,将回包直接发给客户端
RS2->用户:src(10.15.144.120:80) dst(10.15.62.204:63565)
```
从转发效率来讲,NAT 最差;TUN 多了 ip 隧道的处理,次之;DR 效率最高。
## 配置脚本
以 DR 模式为例
```sh
# 安装 LVS 机器(即 DirectorServer)脚本,lvs-DR.sh
# 设置 VIP,并设置转发规则
#!/bin/sh
VIP=10.15.144.120
RIP1=10.15.144.102
RIP2=10.15.144.103
RIP3=10.15.144.104
. /etc/rc.d/init.d/functions
case "$1" in
start)
echo " start LVS of Director Server"
/sbin/ifconfig eth0:1 $VIP broadcast $VIP netmask 255.255.255.255 up # 添加虚拟设备 eth0:1 和虚拟 IP
/sbin/route add -host $VIP dev eth0:1
echo "1" >/proc/sys/net/ipv4/ip_forward # 允许转发
/sbin/ipvsadm -C #Clear IPVS table
#set LVS
/sbin/ipvsadm -A -t $VIP:10087 -s wrr -p 60
/sbin/ipvsadm -a -t $VIP:10087 -r $RIP1 -g -w 2 # -g 为 DR 模式, -w 为权重
/sbin/ipvsadm -a -t $VIP:10087 -r $RIP2 -g -w 1
/sbin/ipvsadm -a -t $VIP:10087 -r $RIP3 -g -w 1
/sbin/ipvsadm # 打印 ipvs 信息
;;
stop)
echo "close LVS Directorserver"
echo "0" >/proc/sys/net/ipv4/ip_forward
/sbin/ipvsadm -C
/sbin/ifconfig eth0:1 down
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac
#-----------------------------------------------------------------------
# 在 3 台 RealServer 上,配置 VIP,关闭对该 VIP 的 ARP 应答,所执行的脚本:rs-DR.sh
#!/bin/bash
VIP=10.15.144.120
. /etc/rc.d/init.d/functions
case "$1" in
start)
echo " Start LVS of Real Server"
/sbin/ifconfig lo:0 $VIP netmask 255.255.255.255 broadcast $VIP up
/sbin/route add -host $VIP dev lo:0
# 忽略收到的 arp 广播
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
# 封装数据包时,忽略源 ip(lvs 服务器 ip), 而是将 VIP 做为源 ip
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce
sysctl -p > /dev/null 2>&1
;;
stop)
/sbin/ifconfig lo:0 down
echo "close LVS Director server"
route del $VIP > /dev/null 2>&1
echo "0" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "0" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "0" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "0" >/proc/sys/net/ipv4/conf/all/arp_announce
;;
*)
echo "Usage: $0 {start|stop}"
exit 1
esac
```
在 DS 和 RS 上运行相应的脚本后,LVS 负载均衡系统就搭建完毕了。
## ipvsadm
利用 ipvs 管理工具 ipvsadm,查看 DS 内部情况
```sh
[root@10.15.144.71 lvs]# ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 10.15.144.120:10087 wrr persistent 2
-> 10.15.144.102:10087 Route 2 0 4
-> 10.15.144.103:10087 Route 1 0 0
-> 10.15.144.104:10087 Route 1 0 0
# 在 10.15.62.204 上进行测试,常用命令有:
ll@ll-rw:~$ wget http://10.15.144.120:10087/index.html
ll@ll-rw:~$ ab -n 100 -c 10 http://10.15.144.120:10087/index.html
[root@10.15.144.71 lvs]# ipvsadm -L -c
IPVS connection entries
pro expire state source virtual destination
TCP 00:22 FIN_WAIT 10.15.62.204:50937 10.15.144.120:10087 10.15.144.102:10087
TCP 00:04 FIN_WAIT 10.15.62.204:50930 10.15.144.120:10087 10.15.144.102:10087
TCP 01:50 FIN_WAIT 10.15.62.204:50959 10.15.144.120:10087 10.15.144.103:10087
TCP 00:50 NONE 10.15.62.204:0 10.15.144.120:10087 10.15.144.103:10087
TCP 00:22 NONE 10.15.62.204:0 10.15.144.120:65535 10.15.144.102:65535
TCP 00:06 FIN_WAIT 10.15.62.204:50931 10.15.144.120:10087 10.15.144.102:10087
# 关掉 RealServer 服务器中的服务,再次在 10.15.62.204 测试时,收到的错误信息
ll@ll-rw:~$ wget http://10.15.144.120:10087/index.html
--2014-11-18 16:47:40-- http://10.15.144.120:10087/index.html
Connecting to 10.15.144.120:10087... failed: No route to host.
```
当 DR 模式时,数据包是直接从 RS 返回到客户端的,所以在 RS 上也需要虚拟出设备和 IP(lo:0 10.15.14.120)。RS 直接利用该 IP 进行返回。
同时,在同一子网内,有多个 Server 都拥有 10.15.14.120 这个 IP。当其它机器进行 ARP 查询时 (who has ip 10.15.1.120),只能够由 DS 进行响应,其他 Server 不能够响应。这也是 RS 需要使用 echo "0" >/proc/sys/net/ipv4/conf/lo/arp_ignore 的原因。
## tcpdump 抓包分析
使用 tcpdump 在 DS 上抓包,可以看到从 User 来的数据包,直接转发给了 RS;RS 收到数据包后,也是直接回复给了 User,不需要再经过 DS 转发。下图是在 RS(10.15.14.102) 上抓包的截图:

## 问题分析
LVS 存在单点故障。当 LVS 服务器挂掉了,整个系统就完了。
LVS 不检测内部服务器的状态。当内部服务器挂掉时,仍然将请求发往该服务器。
**LVS+Keepalived**解决方案:
- 实现主备模式解决单点故障。
- 内部服务器有问题时,将其从可用服务器列表中删除;当其恢复时,再将其加入到可用服务器列表。
================================================
FILE: doc/Linux/README.md
================================================
# Linux 篇
> * Linux 基础
> * Linux 工具
> * Linux 安全
> * Linux 优化
> * 脚本编程 (shell)
> * 常见服务架设
> * 常用问题处理
================================================
FILE: doc/Linux/base.md
================================================
## Linux 基础
<!-- vim-markdown-toc GFM -->
* [安装](#安装)
* [安装准备](#安装准备)
* [安装 CentOS6.8](#安装-centos68)
* [系统安装后的配置](#系统安装后的配置)
* [Bash 基础特性](#bash-基础特性)
* [命令历史](#命令历史)
* [命令补全](#命令补全)
* [路径补全](#路径补全)
* [命令行展开](#命令行展开)
* [命令的执行状态结果](#命令的执行状态结果)
* [命令别名](#命令别名)
* [通配符 glob](#通配符-glob)
* [bash 快捷键](#bash-快捷键)
* [编辑命令](#编辑命令)
* [重新执行命令](#重新执行命令)
* [控制命令](#控制命令)
* [Bang (!) 命令](#bang--命令)
* [友情提示](#友情提示)
* [bash 的 io 重定向及管道](#bash-的-io-重定向及管道)
* [I/O 重定向](#io-重定向)
* [Linux 常用命令](#linux-常用命令)
* [系统](#系统)
* [系统信息](#系统信息)
* [关机](#关机)
* [监视和调试](#监视和调试)
* [公钥私钥](#公钥私钥)
* [其他](#其他)
* [资源](#资源)
* [磁盘空间](#磁盘空间)
* [文件及文本处理](#文件及文本处理)
* [文件和目录](#文件和目录)
* [文件搜索](#文件搜索)
* [文件的权限](#文件的权限)
* [文件的特殊属性](#文件的特殊属性)
* [查看文件内容](#查看文件内容)
* [文本处理](#文本处理)
* [字符设置和文件格式](#字符设置和文件格式)
* [挂载](#挂载)
* [挂载一个文件系统](#挂载一个文件系统)
* [光盘](#光盘)
* [用户管理](#用户管理)
* [用户和群组](#用户和群组)
* [包管理](#包管理)
* [打包和压缩文件](#打包和压缩文件)
* [RPM 包 \(Fedora,RedHat and alike\)](#rpm-包-fedoraredhat-and-alike)
* [YUM 软件工具 \(Fedora,RedHat and alike\)](#yum-软件工具-fedoraredhat-and-alike)
* [备份](#备份)
* [磁盘和分区](#磁盘和分区)
* [文件系统分析](#文件系统分析)
* [初始化一个文件系统](#初始化一个文件系统)
* [SWAP 文件系统](#swap-文件系统)
* [网络](#网络)
* [网络 \(LAN / WiFi\)](#网络-lan--wifi)
* [route 设置](#route-设置)
* [基本使用](#基本使用)
* [在 linux 下设置永久路由的方法](#在-linux-下设置永久路由的方法)
* [Microsoft windows 网络 \(samba\)](#microsoft-windows-网络-samba)
* [IPTABLES \(firewall\)](#iptables-firewall)
* [Linux 简单管理](#linux-简单管理)
* [ssh](#ssh)
* [ssh 简介及基本操作](#ssh-简介及基本操作)
* [简介](#简介)
* [密钥](#密钥)
* [基于口令的安全验证通讯原理](#基于口令的安全验证通讯原理)
* [StrictHostKeyChecking 和 UserKnownHostsFile](#stricthostkeychecking-和-userknownhostsfile)
* [基于密匙的安全验证通讯原理](#基于密匙的安全验证通讯原理)
* [SSH 端口转发](#ssh-端口转发)
* [SSH 正向连接](#ssh-正向连接)
* [SSH 反向连接](#ssh-反向连接)
* [SSH 反向连接自动重连](#ssh-反向连接自动重连)
* [windows 下 xshell 使用](#windows-下-xshell-使用)
* [用户管理](#用户管理-1)
* [Linux 踢出其他正在 SSH 登陆用户](#linux-踢出其他正在-ssh-登陆用户)
* [使用脚本创建有 sudo 权限的用户](#使用脚本创建有-sudo-权限的用户)
* [无交互式修改用户密码](#无交互式修改用户密码)
* [网卡 bond](#网卡-bond)
* [其他设置](#其他设置)
* [时区及时间](#时区及时间)
* [UTC 和 GMT](#utc-和-gmt)
* [Linux 下调整时区及更新时间](#linux-下调整时区及更新时间)
* [登录提示信息](#登录提示信息)
* [修改登录前的提示信息](#修改登录前的提示信息)
* [修改登录成功后的信息](#修改登录成功后的信息)
* [CentOS 7 vs CentOS 6 的不同](#centos-7-vs-centos-6-的不同)
* [运行相关](#运行相关)
* [网络](#网络-1)
<!-- vim-markdown-toc -->
# 安装
CentOS 6.x
## 安装准备
1. 下载安装镜像文件
http://www.centos.org ->downloads->mirrors
http://mirrors.aliyun.com/centos/6.8/isos/x86_64/
http://mirrors.aliyun.com/centos/6.8/isos/i386/
主要下载 Centos-6.8-x86_64-bin-DVD1.iso 和 Centos-6.8-x86_64-bin-DVD2.iso
## 安装 CentOS6.8
***选择系统引导方式***
选择 install or upgrade an existing system
***检查安装光盘介质***
选择:skip
***选择安装过程语言***
选择:english
***选择键盘布局***
选择:U.S.English
***选择合适的物理设备***
选择:basic storage devices
***初始化硬盘提示***
选择:yes ,discard and data
***初始化主机名以及网络配置***
(1). 为系统设置主机名 主机名为:meetbill
(2). 配置网卡及连接网络(可选)
***系统时钟及时区***
选择:Asia/Shanghai
取消:system clock uses UTC
然后:next
***设置 root 口令***
***磁盘分区类型选择与磁盘分区配置过程***
(1) 选择系统安装磁盘空间类型
选择:create custom layout
(2) 进入 'create custom layout'分区界面
可以 create (创建),update(修改) ,delete(删除)等操作。
(3) 按企业生产标准定制磁盘分区
选择:standard partition
1). 创建引导分区,/boot 分区
mount point:/boot
file system type:ext4
size:200
2). 创建 swap 交换分区
mount point :<not applicable>
file system type:swap
size:1024 (物理内存的 1-2 倍)
addtion size options : fixed size
force to be a primary partition
3). 创建 ( / ) 根分区
mount point :/
file system type : ext4
size : 剩余
addtion size options : fill to maximum allowable size (根分区是最后一个分区,所以把剩余的空间都分配给根分区)
force to be a primary partition
4). 格式化警告
选择: format
***系统安装包的选择与配置***
(1) 启动引导设备的配置
系统默认使用 GRUB 作为启动加载器,引导程序默认在 MBR 下:
```
install boot loader on /dev/sda ->change device
选择 master boot record -/dev/sda
[选择的是操作系统所在的那个设备,如 /dev/sda]
Boot Loader operation system list
列表中选择的是操作系统根目录 / 所在的分区,如 CentOS /dev/sda4
```
(2) 系统安装类型选择及自定义额外包组
系统默认是 desktop ,但是这里选择 minimal。
自定义安装包选择:customsize now
base system :
base
然后:next
***开始安装 ->安装完成 ->reboot***
## 系统安装后的配置
***更新系统,打补丁到最新***
修改更新 yum 源:
cp /etc /yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.ori
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirros.163.com/.help/CentOS 6-Base-163.repo
ll /etc/pki/rpm-gpg/
rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY*
yum update -y
ps: 一般在首次安装时执行 yum update -y , 如果是在实际生产环境中,切记使用,以免导致异常。
***安装额外的软件包***
yum install tree telnet dos2unix sysstat lrzsz nc nmap -y
yum grouplist #查看包组列表
yum frouplist "development Tools"
# Bash 基础特性
## 命令历史
(1) 使用命令:history
(2) 环境变量:
a) HISTSIZE:命令历史缓冲区中记录的条数,默认为 1000;
b) HISTFILE:记录当前登录用户在 logout 时历史命令存放文件;
c) HISTFILESIZE:命令历史文件记录历史的条数,默认为 1000;
(3) 操作命令历史:
a) history –d OFFSET 删除指定行的命令历史;
b) history –c 清空命令历史缓冲区中的命令;
c) history # 显示历史中最近的#条命令;
d) history –a 手动追加当前会话缓冲区中的命令至历史文件中;
(4) 调用历史中的命令:
a) !# : 重复执行第#条命令;
b) !! : 重复执行上一条(最近一条命令;)
c) !string : 重复执行最近一次以指定字符串开头的命令;
d) 调用上一条命令的最后一个参数:
i. !$
ii. ESC, .
(5) 控制命令历史的记录方式:
环境变量:HISTCONTROL
三个值:
ignoredups:忽略重复的命令;所谓重复,一定是连续且完全相同,包括选项和参数;
ignorespace:忽略所有以空白开头的命令,不记录;
ignoreboth:忽略上述两项,既忽略重复的命令,也忽略空白开头的命令;
- 修改环境变量的方式:
export 变量名 =“VALUE”
或: VARNAME=“VALUE” export VARNAME
## 命令补全
内部命令:直接通过 shell 补全;
外部命令:bash 根据 PATH 环境变量定义的路径,自左而右地在每个路径搜寻以给定命令命名的文件,第一次找到即为要执行的命令;
- Note: 在第一次通过 PATH 搜寻到命令后,会将其存入 hash 缓存中,下次使用不再搜寻 PATH,从 hash 中查找;
```
[root@sslinux ~]# hash
hits command
1 /usr/sbin/ifconfig
1 /usr/bin/vim
1 /usr/bin/ls
```
Tab 键补全:
若用户给出的字符在命令搜索路径中有且仅有一条命令与之相匹配,则 Tab 键直接补全;
若用户输入的字符在命令搜索路径中有多条命令与之相匹配,则再次 Tab 键可以将这些命令列出;
## 路径补全
以用户输入的字符串作为路径开头,并在其指定路径的上级目录下搜索以指定字符串开头的文件名;
如果唯一,则直接补全;
否则,再次 Tab,列出所有符合条件的路径及文件;
## 命令行展开
1)~ :展开为用户的主目录;
~~~shell
[root@sslinux log]# pwd
/var/log
[root@sslinux log]# cd ~
[root@sslinux ~]# pwd
/root
~~~
2)~USERNAME : 展开为指定用户的主目录;
~~~shell
[root@sslinux ~]# pwd
/root
[root@sslinux ~]# cd ~sslinux
[root@sslinux sslinux]# pwd
/home/sslinux
~~~
3) {} : 可承载一个以逗号分隔的列表,并将其展开为多个路径;
~~~shell
[root@localhost test]# ls
[root@localhost test]# mkdir -pv ./tmp/{a,b}/shell
mkdir: created directory `./tmp'
mkdir: created directory `./tmp/a'
mkdir: created directory `./tmp/a/shell'
mkdir: created directory `./tmp/b'
mkdir: created directory `./tmp/b/shell'
[root@localhost test]# mkdir -pv ./tmp/{tom,johnson}/hi
[root@localhost test]# tree .
└── tmp
├── a
│ └── shell
├── b
│ └── shell
├── johnson
│ └── hi
└── tom
└── hi
9 directories, 0 files
~~~
## 命令的执行状态结果
表示命令是否成功执行;
bash 使用特殊变量 $? 保存最近一条命令的执行状态结果;
- 环境变量 $? 的取值:
0 : 成功;
1-255:失败,1,127,255 为系统保留;
- 程序执行有两类结果:
程序的返回值;程序自身执行的输出结果;
程序的执行状态结果;$?
~~~shell
[root@localhost test]# ls /etc/sysconfig/
[root@localhost test]# echo $?
0 #程序的执行状态结果;执行成功;
[root@localhost test]# ls /etc/sysconfig/NNNN
ls: cannot access /etc/sysconfig/NNNN: No such file or directory #程序自身的执行结果;
[root@localhost test]# echo $?
2 #执行失败;
~~~
## 命令别名
- 通过 alias 命令实现:
a、alias : 显示当前 shell 进程所有可用的命令别名;
b、定义别名,格式为: alias NAME='VALUE'
定义别名 NAME,其执行相当于执行命令 VALUE,VALUE 中可包含命令、选项以及参数;仅当前会话有效,不建议使用;
c、通过修改配置文件定义命令别名:
当前用户:~/.bashrc
全局用户:/etc/bashrc
- Bash 进程重新读取配置文件:
~~~shell
source /path/to/config_file
. /path/to/config_file
~~~
- 撤销别名: unalias
```
unalias [-a] name [name...]
```
- Note:
对于定义了别名的命令,要使用原命令,可通过、COMMAND 的方式使用;
- Example:
```
[root@sslinux sslinux]# alias
alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias mv='mv -i'
alias rm='rm -i'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
[root@sslinux sslinux]# grep alias /root/.bashrc
### User specific aliases and functions
alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'
```
## 通配符 glob
Bash 中用于文件名"通配"
- 通配符: *,?,[]
1) * 任意长度的任意字符;
a * b
```
[root@sslinux sslinux]# ls -ld /etc/au*
drwxr-x---. 3 root root 41 Sep 3 22:05 /etc/audisp
drwxr-x---. 3 root root 79 Sep 3 22:09 /etc/audit
```
2) ? 任意单个字符;
a?b
~~~shell
[root@sslinux sslinux]# ls -ld /etc/*d?t
drwxr-x---. 3 root root 79 Sep 3 22:09 /etc/audit
~~~
3) [] 匹配指定范围内的任意单个字符;
[0-9] [a-z] 不区分大小写;
[admin] 可以是区间形式的,也可以是离散形式的;
~~~shell
[root@sslinux sslinux]# ls -ld /etc/[ab]*
drwxr-xr-x. 2 root root 4096 Sep 3 22:05 /etc/alternatives
drwxr-xr-x. 2 root root 33 Sep 3 22:04 /etc/avahi
drwxr-xr-x. 2 root root 33 Sep 3 22:04 /etc/bash_completion.d
-rw-r--r--. 1 root root 2835 Oct 29 2014 /etc/bashrc
drwxr-xr-x. 2 root root 6 Mar 6 2015 /etc/binfmt.d
~~~
4) [^] 匹配指定范围以外的任意单个字符;
```
[^0-9] : 单个非数字的任意字符;
```
- 专用字符结合:(表示一类字符中的单个)
[:digit:] 任意单个数字,相当于 [0-9];
[:lower:] 任意单个小写字母;
[:upper:] 任意单个大写字母;
[:alpha:] 任意单个大小写字母;
[:alnum:] 任意单个数字或字母;
[:space:] 任意空白字符;
[:punct:] 任意单个特殊字符;
- Note:
在使用 [] 应用专用字符集合时,外层也需要嵌套 []。
Example:
```
# ls -d /etc/*[[:digit:]]*[[:lower:]]
```
## bash 快捷键
### 编辑命令
- Ctrl + a :移到命令行首
- Ctrl + e :移到命令行尾
- Ctrl + f :按字符前移(右向)
- Ctrl + b :按字符后移(左向)
- Alt + f :按单词前移(右向)
- Alt + b :按单词后移(左向)
- Ctrl + xx:在命令行首和光标之间移动
- Ctrl + u :从光标处删除至命令行首
- Ctrl + k :从光标处删除至命令行尾
- Ctrl + w :从光标处删除至字首
- Alt + d :从光标处删除至字尾
- Ctrl + d :删除光标处的字符
- Ctrl + h :删除光标前的字符
- Ctrl + y :粘贴至光标后
- Alt + c :从光标处更改为首字母大写的单词
- Alt + u :从光标处更改为全部大写的单词
- Alt + l :从光标处更改为全部小写的单词
- Ctrl + t :交换光标处和之前的字符
- Alt + t :交换光标处和之前的单词
- Alt + Backspace:与 Ctrl + w 相同类似,分隔符有些差别
### 重新执行命令
- Ctrl + r:逆向搜索命令历史
- Ctrl + g:从历史搜索模式退出
- Ctrl + p:历史中的上一条命令
- Ctrl + n:历史中的下一条命令
- Alt + .:使用上一条命令的最后一个参数
### 控制命令
- Ctrl + l:清屏
- Ctrl + o:执行当前命令,并选择上一条命令
- Ctrl + s:阻止屏幕输出
- Ctrl + q:允许屏幕输出
- Ctrl + c:终止命令
- Ctrl + z:挂起命令
### Bang (!) 命令
- !!:执行上一条命令
- !blah:执行最近的以 blah 开头的命令,如 !ls
- !blah:p:仅打印输出,而不执行
- !$:上一条命令的最后一个参数,与 Alt + . 相同
- !$:p:打印输出 !$ 的内容
- !*:上一条命令的所有参数
- !*:p:打印输出 !* 的内容
- ^blah:删除上一条命令中的 blah
- ^blah^foo:将上一条命令中的 blah 替换为 foo
- ^blah^foo^:将上一条命令中所有的 blah 都替换为 foo
### 友情提示
以上介绍的大多数 Bash 快捷键仅当在 emacs 编辑模式时有效,
若你将 Bash 配置为 vi 编辑模式,那将遵循 vi 的按键绑定。
Bash 默认为 emacs 编辑模式。
如果你的 Bash 不在 emacs 编辑模式,可通过 set-o emacs 设置。
^S、^Q、^C、^Z 是由终端设备处理的,可用 stty 命令设置。
## bash 的 io 重定向及管道
打开的文件都有一个 fd:file descriptor(文件描述符)
标准输入:keyboard,0
标准输出:monitor,1
标准错误输出:monitor,2
### I/O 重定向
- 输出重定向:
COMMAND > NEW_POS 覆盖重定向,目标文件中的原有内容会被清除;
COMMAND >> NEW_POS 追加重定向,新内容会被追加到目标文件尾部;
- Note:
为了在输出重定向时防止覆盖原有文件,建议使用以下设置:
set –C : 禁止将内容覆盖输出 (>) 至已有文件中,追加输出不受影响;
此时,若确定要将重定向的内容覆盖原有文件,可使用 >| 强制覆盖;
- Example:
~~~shell
[root@localhost test1]# echo "It's dangerous" > ./result.txt #输出到文件;
[root@localhost test1]# cat result.txt
It's dangerous
[root@localhost test1]# set –C #禁止将内容覆盖输出到已有文件;
[root@localhost test1]# echo "It's very dangerous" > ./result.txt
-bash: ./result.txt: cannot overwrite existing file #提示不能覆盖已存在文件;
[root@localhost test1]# echo "It's very dangerous" >| ./result.txt #强制覆盖
[root@localhost test1]#
[root@localhost test1]# set +C #取消禁止覆盖输出到已有文件;
[root@localhost test1]# echo "It's very dangerous" > ./result.txt
[root@localhost test1]#
~~~
- 错误输出:
2> : 覆盖重定向错误输出数据流;
2>> :追加重定向错误输出数据流;
~~~shell
[root@localhost test1]# lss -l /etc/ 2> ./error.txt
[root@localhost test1]# cat error.txt
-bash: lss: command not found
[root@localhost test1]# cat /etc/passwd.error 2>> ./error.txt
[root@localhost test1]# cat error.txt
-bash: lss: command not found
cat: /etc/passwd.error: No such file or directory
~~~
将标准输出和标准错误输出各自重定向至不同位置:
COMMAND > /path/to/file.out 2> /path/to/error.out
- Example:
```
# cat /etc/passwd > ./file.out 2> ./error.out
```
- 合并输出:
合并标准输出和错误输出为同一个数据流进行重定向;(PS:重定向命令是倒序操作的,如 > file 2>&1 是先执行 2>&1 然后执行 > file)
&> 合并覆盖重定向;
&>> 合并追加重定向;
格式为:
COMMAND > /path/to/file.out 2> &1
COMMAND >> /path/to/file.out 2>> &1
Example:
~~~shell
[root@localhost test1]# ls -l /etc/ > ./file.out 2>&1
[root@localhost test1]# ls -l /etc/ &> file.out
[root@localhost test1]# ls -l /etcc/ &> file.out
[root@localhost test1]# cat file.out
ls: cannot access /etcc/: No such file or directory
~~~
- 输入重定向: <
~~~shell
HERE Documentation:<<
# cat << EOF
# cat > /path/to/somefile << EOF
~~~
Example: 输入重定向,输入完成后显示内容到标准输出上;
~~~shell
[root@localhost test1]# cat << EOF
> my name is kalaguiyin.
> I'm a tibetan.
> I come from Sichuan Provence.
> EOF
my name is kalaguiyin.
I'm a tibetan.
I come from Sichuan Provence.
~~~
Example:从标准输入读取输入并重定向到文件。
~~~shell
[root@localhost test1]# cat > hello.txt << EOF
> this is a test file.
> 中华人民共和国。
> EOF
[root@localhost test1]# cat hello.txt
this is a test file.
中华人民共和国。
~~~
- 管道:
COMMAND1 | COMMAND2 | COMMAND3 | …..
作用:前一个命令的执行结果将作为后一个命令执行的参数;
Note:
最后一个命令会在当前 shell 进程的子 shell 进程中执行;
~~~shell
[root@sslinux]# cat /etc/passwd | sort -t: -k3 -n | cut -d: -f1
root
bin
daemon
adm
lp
polkitd
sslinux
~~~
# Linux 常用命令
## 系统
### 系统信息
| 命令 | 说明 |
|--------|--------|
|\# arch|显示机器的处理器架构|
|\# cal 2016|显示 2016 年的日历表|
|\# cat /proc/cpuinfo|查看 CPU 信息|
|\# cat /proc/interrupts|显示中断|
|\# cat /proc/meminfo|校验内存使用|
|\# cat /proc/swaps|显示哪些 swap 被使用|
|\# cat /proc/version|显示内核版本|
|\# cat /proc/net/dev|显示网络适配器及统计|
|\# cat /proc/mounts|显示已加载的文件系统|
|\# clock \-w|将时间修改保存到 BIOS|
|\# date|显示系统日期|
|\# date 072308302016\.00|设置日期和时间 \- 月日时分年、. 秒|
|\# dmidecode \-q|显示硬件系统部件 \- \(SMBIOS / DMI\)|
|\# hdparm \-i /dev/hda|罗列一个磁盘的架构特性|
|\# hdparm \-tT /dev/sda|在磁盘上执行测试性读取操作|
|\# lspci \-tv|罗列 PCI 设备|
|\# lsusb \-tv|显示 USB 设备|
|\# uname \-m|显示机器的处理器架构|
|\# uname \-r|显示正在使用的内核版本|
### 关机
| 命令 | 说明 |
|--------|--------|
|\# init 0|关闭系统|
|\# logout|注销|
|\# reboot|重启|
|\# shutdown \-h now|关闭系统|
|\# shutdown \-h 16:30 &|按预定时间关闭系统|
|\# shutdown \-c|取消按预定时间关闭系统|
|\# shutdown \-r now|重启|
### 监视和调试
| 命令 | 说明 |
|--------|--------|
|\# free \-m|以兆为单位罗列 RAM 状态|
|\# kill \-9 process\_id|强行关闭进程并结束它|
|\# kill \-1 process\_id|强制一个进程重载其配置|
|\# last reboot|显示重启历史|
|\# lsmod|罗列装载的内核模块|
|\# lsof \-p process\_id|罗列一个由进程打开的文件列表|
|\# lsof /home/user1|罗列所给系统路径中所打开的文件的列表|
|\# ps \-eafw|罗列 linux 任务|
|\# ps \-e \-o pid,args \-\-forest|以分级的方式罗列 linux 任务|
|\# pstree|以树状图显示程序|
|\# smartctl \-A /dev/hda|通过启用 SMART 监控硬盘设备的可靠性|
|\# smartctl \-i /dev/hda|检查一个硬盘设备的 SMART 是否启用|
|\# strace \-c ls >/dev/null|罗列系统 calls made 并用一个进程接收|
|\# strace \-f \-e open ls >/dev/null|罗列库调用|
|\# tail /var/log/dmesg|显示内核引导过程中的内部事件|
|\# tail /var/log/messages|显示系统事件|
|\# top|罗列使用 CPU 资源最多的 linux 任务|
|\# watch \-n1 'cat /proc/interrupts'|罗列实时中断|
### 公钥私钥
| 命令 | 说明 |
|--------|--------|
| \# ssh-keygen -t rsa -C "邮箱地址" | 产生公钥私钥对 |
| \# ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.0.2 | 将本地机器的公钥复制到远程机器的 root 用户的 authorized_keys 文件中 |
| \# ssh-keygen -p -f ~/.ssh/id_rsa | 添加或修改 SSH-key 的私钥密码 |
| \# ssh-keygen -y -f ~/.ssh/id_rsa > id_rsa.pub | 从私钥中生成公钥 |
### 其他
| 命令 | 说明 |
|--------|--------|
|\# alias hh='history'|为命令 history\(历史、) 设置一个别名|
|\# gpg \-c file1|用 GNU Privacy Guard 加密一个文件|
|\# gpg file1\.gpg|用 GNU Privacy Guard 解密一个文件|
|\# ldd /usr/bin/ssh|显示 ssh 程序所依赖的共享库|
|\# man ping|罗列在线手册页(例如 ping 命令)|
|\# mkbootdisk \-\-device /dev/fd0 \`uname \-r\`|创建一个引导软盘|
|\# wget \-r www\.example\.com|下载一个完整的 web 站点|
|\# wget \-c www\.example\.com/file\.iso|以支持断点续传的方式下载一个文件|
|\# echo 'wget \-c www\.example\.com/files\.iso' | at 09:00|在任何给定的时间开始一次下载|
|\# whatis \.\.\.keyword|罗列该程序功能的说明|
|\# who \-a|显示谁正登录在线,并打印出:系统最后引导的时间,关机进程,系统登录进程以及由 init 启动的进程,当前运行级和最后一次系统时钟的变化|
## 资源
### 磁盘空间
| 命令 | 说明 |
|--------|--------|
|\# df \-h|显示已经挂载的分区列表|
|\# du \-sh dir1|估算目录 'dir1' 已经使用的磁盘空间|
|\# du \-sk \* | sort \-rn|以容量大小为依据依次显示文件和目录的大小|
|\# ls \-lSr | more|以尺寸大小排列文件和目录|
|\# rpm \-q \-a \-\-qf '%10\{SIZE\}t%\{NAME\}n' | sort \-k1,1n|以大小为依据依次显示已安装的 rpm 包所使用的空间 \(centos, redhat, fedora 类系统、)|
## 文件及文本处理
### 文件和目录
| 命令 | 说明 |
|--------|--------|
|\# cd /home|进入 '/home' 目录|
|\# cd \.\.|返回上一级目录|
|\# cd \.\./\.\.|返回上两级目录|
|\# cd|进入个人的主目录|
|\# cd ~user1|进入个人的主目录|
|\# cd \-|返回上次所在的目录|
|\# cp file1 file2|复制一个文件|
|\# cp dir/\* \.|复制一个目录下的所有文件到当前工作目录|
|\# cp \-a /tmp/dir1 \.|复制一个目录到当前工作目录|
|\# cp \-a dir1 dir2|复制一个目录|
|\# cp file file1|将 file 复制为 file1|
|\# iconv \-l|列出已知的编码|
|\# iconv \-f fromEncoding \-t toEncoding inputFile > outputFile|改变字符的编码|
|\# find \. \-maxdepth 1 \-name \*\.jpg \-print \-exec convert|batch resize files in the current directory and send them to a thumbnails directory (requires convert from Imagemagick)|
|\# ln \-s file1 lnk1|创建一个指向文件或目录的软链接|
|\# ln file1 lnk1|创建一个指向文件或目录的物理链接|
|\# ls|查看目录中的文件|
|\# ls \-F|查看目录中的文件|
|\# ls \-l|显示文件和目录的详细资料|
|\# ls \-a|显示隐藏文件|
|\# ls \*\[0\-9\]\*|显示包含数字的文件名和目录名|
|\# lstree|显示文件和目录由根目录开始的树形结构|
|\# mkdir dir1|创建一个叫做 'dir1' 的目录|
|\# mkdir dir1 dir2|同时创建两个目录|
|\# mkdir \-p /tmp/dir1/dir2|创建一个目录树|
|\# mv dir1 new\_dir|重命名 / 移动 一个目录|
|\# pwd|显示工作路径|
|\# rm \-f file1|删除一个叫做 'file1' 的文件|
|\# rm \-rf dir1|删除一个叫做 'dir1' 的目录并同时删除其内容|
|\# rm \-rf dir1 dir2|同时删除两个目录及它们的内容|
|\# rmdir dir1|删除一个叫做 'dir1' 的目录|
|\# touch \-t 1607230000 file1|修改一个文件或目录的时间戳 \- \(YYMMDDhhmm\)|
|\# tree|显示文件和目录由根目录开始的树形结构|
### 文件搜索
| 命令 | 说明 |
|--------|--------|
|\# find / \-name file1|从 '/' 开始进入根文件系统搜索文件和目录|
|\# find / \-user user1|搜索属于用户 'user1' 的文件和目录|
|\# find /home/user1 \-name \\\*\.bin|在目录 '/ home/user1' 中搜索带有'\.bin' 结尾的文件|
|\# find /usr/bin \-type f \-atime \+100|搜索在过去 100 天内未被使用过的执行文件|
|\# find /usr/bin \-type f \-mtime \-10|搜索在 10 天内被创建或者修改过的文件|
|\# find / \-name \*\.rpm \-exec chmod 755 '\{\}' \\;|搜索以 '\.rpm' 结尾的文件并定义其权限|
|\# find / \-xdev \-name \\\*\.rpm|搜索以 '\.rpm' 结尾的文件,忽略光驱、捷盘等可移动设备|
|\# locate \\\*\.ps|寻找以 '\.ps' 结尾的文件 \- 先运行 'updatedb' 命令|
|\# whereis halt|显示一个二进制文件、源码或 man 的位置|
|\# which halt|显示一个二进制文件或可执行文件的完整路径|
### 文件的权限
| 命令 | 说明 |
|--------|--------|
|\# chgrp group1 file1|改变文件的群组|
|\# chmod ugo\+rwx directory1|设置目录的所有人、(u\)、群组、(g\) 以及其他人、(o\) 以读、(r\)、写、(w\) 和执行、(x\) 的权限|
|\# chmod go\-rwx directory1|删除群组、(g\) 与其他人、(o\) 对目录的读写执行权限|
|\# chmod u\+s /bin/file1|设置一个二进制文件的 SUID 位 \- 运行该文件的用户也被赋予和所有者同样的权限|
|\# chmod u\-s /bin/file1|禁用一个二进制文件的 SUID 位|
|\# chmod g\+s /home/public|设置一个目录的 SGID 位 \- 类似 SUID,不过这是针对目录的|
|\# chmod g\-s /home/public|禁用一个目录的 SGID 位|
|\# chmod o\+t /home/public|设置一个文件的 STIKY 位 \- 只允许合法所有人删除文件|
|\# chmod o\-t /home/public|禁用一个目录的 STIKY 位|
|\# chown user1 file1|改变一个文件的所有人属性|
|\# chown \-R user1 directory1|改变一个目录的所有人属性并同时改变改目录下所有文件的属性|
|\# chown user1:group1 file1|改变一个文件的所有人和群组属性|
|\# find / \-perm \-u\+s|罗列一个系统中所有使用了 SUID 控制的文件|
|\# ls \-lh|显示权限|
|\# ls /tmp | pr \-T5 \-W$COLUMNS|将终端划分成 5 栏显示|
### 文件的特殊属性
| 命令 | 说明 |
|--------|--------|
|\# chattr \+a file1|只允许以追加方式读写文件|
|\# chattr \+c file1|允许这个文件能被内核自动压缩 / 解压|
|\# chattr \+d file1|在进行文件系统备份时,dump 程序将忽略这个文件|
|\# chattr \+i file1|设置成不可变的文件,不能被删除、修改、重命名或者链接|
|\# chattr \+s file1|允许一个文件被安全地删除|
|\# chattr \+S file1|一旦应用程序对这个文件执行了写操作,使系统立刻把修改的结果写到磁盘|
|\# chattr \+u file1|若文件被删除,系统会允许你在以后恢复这个被删除的文件|
|\# lsattr|显示特殊的属性|
### 查看文件内容
| 命令 | 说明 |
|--------|--------|
|\# cat file1|从第一个字节开始正向查看文件的内容|
|\# head \-2 file1|查看一个文件的前两行|
|\# less file1|类似于 'more' 命令,但是它允许在文件中和正向操作一样的反向操作|
|\# more file1|查看一个长文件的内容|
|\# tac file1|从最后一行开始反向查看一个文件的内容|
|\# tail \-2 file1|查看一个文件的最后两行|
|\# tail \-f /var/log/messages|实时查看被添加到一个文件中的内容|
### 文本处理
| 命令 | 说明 |
|--------|--------|
|\# cat example\.txt | awk 'NR%2==1'|删除 example\.txt 文件中的所有偶数行|
|\# echo a b c | awk '\{print $1\}'|查看一行第一栏|
|\# echo a b c | awk '\{print $1,$3\}'|查看一行的第一和第三栏|
|\# cat \-n file1|标示文件的行数|
|\# comm \-1 file1 file2|比较两个文件的内容只删除 'file1' 所包含的内容|
|\# comm \-2 file1 file2|比较两个文件的内容只删除 'file2' 所包含的内容|
|\# comm \-3 file1 file2|比较两个文件的内容只删除两个文件共有的部分|
|\# diff file1 file2|找出两个文件内容的不同处|
|\# grep Aug /var/log/messages|在文件 '/var/log/messages'中查找关键词"Aug"|
|\# grep ^Aug /var/log/messages|在文件 '/var/log/messages'中查找以"Aug"开始的词汇|
|\# grep \[0\-9\] /var/log/messages|选择 '/var/log/messages' 文件中所有包含数字的行|
|\# grep Aug \-R /var/log/\*|在目录 '/var/log' 及随后的目录中搜索字符串"Aug"|
|\# paste file1 file2|合并两个文件或两栏的内容|
|\# paste \-d '\+' file1 file2|合并两个文件或两栏的内容,中间用"\+"区分|
|\# sdiff file1 file2|以对比的方式显示两个文件的不同|
|\# sed 's/string1/string2/g' example\.txt|将 example\.txt 文件中的 "string1" 替换成 "string2"|
|\# sed '/^$/d' example\.txt|从 example\.txt 文件中删除所有空白行|
|\# sed '/ \*|\#/d; /^$/d' example\.txt|去除文件 example\.txt 中的注释与空行|
|\# sed \-e '1d' exampe\.txt|从文件 example\.txt 中排除第一行|
|\# sed \-n '/string1/p'|查看只包含词汇 "string1"的行|
|\# sed \-e 's/ \*$//' example\.txt|删除每一行最后的空白字符|
|\# sed \-e 's/string1//g' example\.txt|从文档中只删除词汇 "string1" 并保留剩余全部|
|\# sed \-n '1,5p' example\.txt|显示文件 1 至 5 行的内容|
|\# sed \-n '5p;5q' example\.txt|显示 example\.txt 文件的第 5 行内容|
|\# sed \-e 's/00\*/0/g' example\.txt|用单个零替换多个零|
|\# sort file1 file2|排序两个文件的内容|
|\# sort file1 file2 | uniq|取出两个文件的并集、(重复的行只保留一份、)|
|\# sort file1 file2 | uniq \-u|删除交集,留下其他的行|
|\# sort file1 file2 | uniq \-d|取出两个文件的交集、(只留下同时存在于两个文件中的文件、)|
|\# echo 'word' | tr '\[:lower:\]' '\[:upper:\]'|合并上下单元格内容|
### 字符设置和文件格式
| 命令 | 说明 |
|--------|--------|
|\# dos2unix filedos\.txt fileunix\.txt|将一个文本文件的格式从 MSDOS 转换成 UNIX|
|\# recode \.\.HTML < page\.txt > page\.html|将一个文本文件转换成 html|
|\# recode \-l | more|显示所有允许的转换格式|
|\# unix2dos fileunix\.txt filedos\.txt|将一个文本文件的格式从 UNIX 转换成 MSDOS|
## 挂载
### 挂载一个文件系统
| 命令 | 说明 |
|--------|--------|
|\# fuser \-km /mnt/hda2|当设备繁忙时强制卸载|
|\# mount /dev/hda2 /mnt/hda2|挂载一个叫做 hda2 的盘 \- 确保目录 '/mnt/hda2' 已经存在|
|\# mount /dev/fd0 /mnt/floppy|挂载一个软盘|
|\# mount /dev/cdrom /mnt/cdrom|挂载一个 cdrom 或 dvdrom|
|\# mount /dev/hdc /mnt/cdrecorder|挂载一个 cdrw 或 dvdrom|
|\# mount /dev/hdb /mnt/cdrecorder|挂载一个 cdrw 或 dvdrom|
|\# mount \-o loop file\.iso /mnt/cdrom|挂载一个文件或 ISO 镜像文件|
|\# mount \-t vfat /dev/hda5 /mnt/hda5|挂载一个 Windows FAT32 文件系统|
|\# mount /dev/sda1 /mnt/usbdisk|挂载一个 U 盘或闪存设备|
|\# mount \-t smbfs \-o username=user,password=pass //WinClient/share /mnt/share|挂载一个 windows 网络共享|
|\# umount /dev/hda2|卸载一个叫做 hda2 的盘 \- 先从挂载点 '/mnt/hda2' 退出|
|\# umount \-n /mnt/hda2|运行卸载操作而不写入 /etc/mtab 文件、- 当文件为只读或当磁盘写满时非常有用|
### 光盘
| 命令 | 说明 |
|--------|--------|
|\# cd\-paranoia \-B|从一个 CD 光盘转录音轨到 wav 文件中|
|\# cd\-paranoia \-\-|从一个 CD 光盘转录音轨到 wav 文件中(参数、-3)|
|\# cdrecord \-v gracetime=2 dev=/dev/cdrom \-eject blank=fast \-force|清空一个可复写的光盘内容|
|\# cdrecord \-v dev=/dev/cdrom cd\.iso|刻录一个 ISO 镜像文件|
|\# gzip \-dc cd\_iso\.gz | cdrecord dev=/dev/cdrom \-|刻录一个压缩了的 ISO 镜像文件|
|\# cdrecord \-\-scanbus|扫描总线以识别 scsi 通道|
|\# dd if=/dev/hdc | md5sum|校验一个设备的 md5sum 编码,例如一张 CD|
|\# mkisofs /dev/cdrom > cd\.iso|在磁盘上创建一个光盘的 iso 镜像文件|
|\# mkisofs /dev/cdrom | gzip > cd\_iso\.gz|在磁盘上创建一个压缩了的光盘 iso 镜像文件|
|\# mkisofs \-J \-allow\-leading\-dots \-R \-V|创建一个目录的 iso 镜像文件|
|\# mount \-o loop cd\.iso /mnt/iso|挂载一个 ISO 镜像文件|
## 用户管理
### 用户和群组
| 命令 | 说明 |
|--------|--------|
|\# chage \-E 2016\-12\-31 user1|设置用户口令的失效期限|
|\# groupadd \[group\]|创建一个新用户组|
|\# groupdel \[group\]|删除一个用户组|
|\# groupmod \-n moon sun|重命名一个用户组|
|\# grpck|检查 '/etc/passwd' 的文件格式和语法修正以及存在的群组|
|\# newgrp \- \[group\]|登陆进一个新的群组以改变新创建文件的预设群组|
|\# passwd|修改口令|
|\# passwd user1|修改一个用户的口令 \(只允许 root 执行、)|
|\# pwck|检查 '/etc/passwd' 的文件格式和语法修正以及存在的用户|
|\# useradd \-c "User Linux" \-g admin \-d /home/user1 \-s /bin/bash user1|创建一个属于 "admin" 用户组的用户|
|\# useradd user1|创建一个新用户|
|\# userdel \-r user1|删除一个用户 \( '\-r' 排除主目录、)|
|\# usermod \-c "User FTP" \-g system \-d /ftp/user1 \-s /bin/nologin user1|修改用户属性|
## 包管理
### 打包和压缩文件
| 命令 | 说明 |
|--------|--------|
|\# bunzip2 file1\.bz2|解压一个叫做 'file1\.bz2'的文件|
|\# bzip2 file1|压缩一个叫做 'file1' 的文件|
|\# gunzip file1\.gz|解压一个叫做 'file1\.gz'的文件|
|\# gzip file1|压缩一个叫做 'file1'的文件|
|\# gzip \-9 file1|最大程度压缩|
|\# rar a file1\.rar test\_file|创建一个叫做 'file1\.rar' 的包|
|\# rar a file1\.rar file1 file2 dir1|同时压缩 'file1', 'file2' 以及目录 'dir1'|
|\# rar x file1\.rar|解压 rar 包|
|\# tar \-cvf archive\.tar file1|创建一个非压缩的 tarball|
|\# tar \-cvf archive\.tar file1 file2 dir1|创建一个包含了 'file1', 'file2' 以及 'dir1'的档案文件|
|\# tar \-tf archive\.tar|显示一个包中的内容|
|\# tar \-xvf archive\.tar|释放一个包|
|\# tar \-xvf archive\.tar \-C /tmp|将压缩包释放到 /tmp 目录下|
|\# tar \-cvfj archive\.tar\.bz2 dir1|创建一个 bzip2 格式的压缩包|
|\# tar \-xvfj archive\.tar\.bz2|解压一个 bzip2 格式的压缩包|
|\# tar \-cvfz archive\.tar\.gz dir1|创建一个 gzip 格式的压缩包|
|\# tar \-xvfz archive\.tar\.gz|解压一个 gzip 格式的压缩包|
|\# unrar x file1\.rar|解压 rar 包|
|\# unzip file1\.zip|解压一个 zip 格式压缩包|
|\# zip file1\.zip file1|创建一个 zip 格式的压缩包|
|\# zip \-r file1\.zip file1 file2 dir1|将几个文件和目录同时压缩成一个 zip 格式的压缩包|
### RPM 包 \(Fedora,RedHat and alike\)
| 命令 | 说明 |
|--------|--------|
|\# rpm \-ivh \[package\.rpm\]|安装一个 rpm 包|
|\# rpm \-ivh \-\-nodeeps \[package\.rpm\]|安装一个 rpm 包而忽略依赖关系警告|
|\# rpm \-U \[package\.rpm\]|更新一个 rpm 包但不改变其配置文件|
|\# rpm \-F \[package\.rpm\]|更新一个确定已经安装的 rpm 包|
|\# rpm \-e \[package\]|删除一个 rpm 包|
|\# rpm \-qa|显示系统中所有已经安装的 rpm 包|
|\# rpm \-qa | grep httpd|显示所有名称中包含 "httpd" 字样的 rpm 包|
|\# rpm \-qi \[package\]|获取一个已安装包的特殊信息|
|\# rpm \-qg "System Environment/Daemons"|显示一个组件的 rpm 包|
|\# rpm \-ql \[package\]|显示一个已经安装的 rpm 包提供的文件列表|
|\# rpm \-qc \[package\]|显示一个已经安装的 rpm 包提供的配置文件列表|
|\# rpm \-q \[package\] \-\-whatrequires|显示与一个 rpm 包存在依赖关系的列表|
|\# rpm \-q \[package\] \-\-whatprovides|显示一个 rpm 包所占的体积|
|\# rpm \-q \[package\] \-\-scripts|显示在安装 / 删除期间所执行的脚本 l|
|\# rpm \-q \[package\] \-\-changelog|显示一个 rpm 包的修改历史|
|\# rpm \-qf /etc/httpd/conf/httpd\.conf|确认所给的文件由哪个 rpm 包所提供|
|\# rpm \-qp \[package\.rpm\] \-l|显示由一个尚未安装的 rpm 包提供的文件列表|
|\# rpm \-\-import /media/cdrom/RPM\-GPG\-KEY|导入公钥数字证书|
|\# rpm \-\-checksig \[package\.rpm\]|确认一个 rpm 包的完整性|
|\# rpm \-qa gpg\-pubkey|确认已安装的所有 rpm 包的完整性|
|\# rpm \-V \[package\]|检查文件尺寸、 许可、类型、所有者、群组、MD5 检查以及最后修改时间|
|\# rpm \-Va|检查系统中所有已安装的 rpm 包、- 小心使用|
|\# rpm \-Vp \[package\.rpm\]|确认一个 rpm 包还未安装|
|\# rpm \-ivh /usr/src/redhat/RPMS/\`arch\`/\[package\.rpm\]|从一个 rpm 源码安装一个构建好的包|
|\# rpm2cpio \[package\.rpm\] | cpio \-\-extract \-\-make\-directories \*bin\*|从一个 rpm 包运行可执行文件|
|\# rpmbuild \-\-rebuild \[package\.src\.rpm\]|从一个 rpm 源码构建一个 rpm 包|
### YUM 软件工具 \(Fedora,RedHat and alike\)
| 命令 | 说明 |
|--------|--------|
|\# yum \-y install \[package\]|下载并安装一个 rpm 包|
|\# yum localinstall \[package\.rpm\]|将安装一个 rpm 包,使用你自己的软件仓库为你解决所有依赖关系|
|\# yum \-y update|更新当前系统中所有安装的 rpm 包|
|\# yum update \[package\]|更新一个 rpm 包|
|\# yum remove \[package\]|删除一个 rpm 包|
|\# yum list|列出当前系统中安装的所有包|
|\# yum repolist|显示可用的仓库|
|\# yum search \[package\]|在 rpm 仓库中搜寻软件包|
|\# yum clean \[package\]|清理 rpm 缓存删除下载的包|
|\# yum clean headers|删除所有头文件|
|\# yum clean all|删除所有缓存的包和头文件|
### 备份
| 命令 | 说明 |
|--------|--------|
|\# find /var/log \-name '\*\.log' | tar cv \-\-files\-from=\- | bzip2 > log\.tar\.bz2|查找所有以 '\.log' 结尾的文件并做成一个 bzip 包|
|\# find /home/user1 \-name '\*\.txt' | xargs cp \-av \-\-target\-directory=/home/backup/ \-\-parents|从一个目录查找并复制所有以 '\.txt' 结尾的文件到另一个目录|
|\# dd bs=1M if=/dev/hda | gzip | ssh user@ip\_addr 'dd of=hda\.gz'|通过 ssh 在远程主机上执行一次备份本地磁盘的操作|
|\# dd if=/dev/sda of=/tmp/file1|备份磁盘内容到一个文件|
|\# dd if=/dev/hda of=/dev/fd0 bs=512 count=1|做一个将 MBR \(Master Boot Record\) 内容复制到软盘的动作|
|\# dd if=/dev/fd0 of=/dev/hda bs=512 count=1|从已经保存到软盘的备份中恢复 MBR 内容|
|\# dump \-0aj \-f /tmp/home0\.bak /home|制作一个 '/home' 目录的完整备份|
|\# dump \-1aj \-f /tmp/home0\.bak /home|制作一个 '/home' 目录的交互式备份|
|\# restore \-if /tmp/home0\.bak|还原一个交互式备份|
|\# rsync \-rogpav \-\-delete /home /tmp|同步两边的目录|
|\# rsync \-rogpav \-e ssh \-\-delete /home ip\_address:/tmp|通过 SSH 通道 rsync|
|\# rsync \-az \-e ssh \-\-delete ip\_addr:/home/public /home/local|通过 ssh 和压缩将一个远程目录同步到本地目录|
|\# rsync \-az \-e ssh \-\-delete /home/local ip\_addr:/home/public|通过 ssh 和压缩将本地目录同步到远程目录|
|\# tar \-Puf backup\.tar /home/user|执行一次对 '/home/user' 目录的交互式备份操作|
|\# \( cd /tmp/local/ && tar c \. \) | ssh \-C user@ip\_addr 'cd /home/share/ && tar x \-p'|通过 ssh 在远程目录中复制一个目录内容|
|\# \( tar c /home \) | ssh \-C user@ip\_addr 'cd /home/backup\-home && tar x \-p'|通过 ssh 在远程目录中复制一个本地目录|
|\# tar cf \- \. | \(cd /tmp/backup ; tar xf \- \)|本地将一个目录复制到另一个地方,保留原有权限及链接|
## 磁盘和分区
### 文件系统分析
| 命令 | 说明 |
|--------|--------|
|\# badblocks \-v /dev/hda1|检查磁盘 hda1 上的坏磁块|
|\# dosfsck /dev/hda1|修复 / 检查 hda1 磁盘上 dos 文件系统的完整性|
|\# e2fsck /dev/hda1|修复 / 检查 hda1 磁盘上 ext2 文件系统的完整性|
|\# e2fsck \-j /dev/hda1|修复 / 检查 hda1 磁盘上 ext3 文件系统的完整性|
|\# fsck /dev/hda1|修复 / 检查 hda1 磁盘上 linux 文件系统的完整性|
|\# fsck\.ext2 /dev/hda1|修复 / 检查 hda1 磁盘上 ext2 文件系统的完整性|
|\# fsck\.ext3 /dev/hda1|修复 / 检查 hda1 磁盘上 ext3 文件系统的完整性|
|\# fsck\.vfat /dev/hda1|修复 / 检查 hda1 磁盘上 fat 文件系统的完整性|
|\# fsck\.msdos /dev/hda1|修复 / 检查 hda1 磁盘上 dos 文件系统的完整性|
### 初始化一个文件系统
| 命令 | 说明 |
|--------|--------|
|\# fdformat \-n /dev/fd0|格式化一个软盘|
|\# mke2fs /dev/hda1|在 hda1 分区创建一个 linux ext2 的文件系统|
|\# mke2fs \-j /dev/hda1|在 hda1 分区创建一个 linux ext3\(日志型、) 的文件系统|
|\# mkfs /dev/hda1|在 hda1 分区创建一个文件系统|
|\# mkfs \-t vfat 32 \-F /dev/hda1|创建一个 FAT32 文件系统|
|\# mkswap /dev/hda3|创建一个 swap 文件系统|
### SWAP 文件系统
| 命令 | 说明 |
|--------|--------|
|\# mkswap /dev/hda3|创建一个 swap 文件系统|
|\# swapon /dev/hda3|启用一个新的 swap 文件系统|
|\# swapon /dev/hda2 /dev/hdb3|启用两个 swap 分区|
## 网络
### 网络 \(LAN / WiFi\)
| 命令 | 说明 |
|--------|--------|
|\# dhclient eth0|以 dhcp 模式启用 'eth0' 网络设备|
|\# ethtool eth0|显示网卡 'eth0' 的流量统计|
|\# host www\.example\.com|查找主机名以解析名称与 IP 地址及镜像|
|\# hostname|显示主机名|
|\# ifconfig eth0|显示一个以太网卡的配置|
|\# ifconfig eth0 192\.168\.1\.1 netmask 255\.255\.255\.0|控制 IP 地址|
|\# ifconfig eth0 promisc|设置 'eth0' 成混杂模式以嗅探数据包 \(sniffing\)|
|\# ifdown eth0|禁用一个 'eth0' 网络设备|
|\# ifup eth0|启用一个 'eth0' 网络设备|
|\# ip link show|显示所有网络设备的连接状态|
|\# iwconfig eth1|显示一个无线网卡的配置|
|\# iwlist scan|显示无线网络|
|\# mii\-tool eth0|显示 'eth0'的连接状态|
|\# netstat \-tup|显示所有启用的网络连接和它们的 PID|
|\# netstat \-tupl|显示系统中所有监听的网络服务和它们的 PID|
|\# netstat \-rn|显示路由表,类似于“route \-n”命令|
|\# nslookup www\.example\.com|查找主机名以解析名称与 IP 地址及镜像|
|\# route \-n|显示路由表|
|\# route add \-net 0/0 gw IP\_Gateway|控制预设网关|
|\# route add \-net 192\.168\.0\.0 netmask 255\.255\.0\.0 gw 192\.168\.1\.1|控制通向网络 '192\.168\.0\.0/16' 的静态路由|
|\# route del 0/0 gw IP\_gateway|删除静态路由|
|\# echo "1" > /proc/sys/net/ipv4/ip\_forward|激活 IP 转发|
|\# tcpdump tcp port 80|显示所有 HTTP 回环|
|\# whois www\.example\.com|在 Whois 数据库中查找|
### route 设置
#### 基本使用
添加到主机的路由(就是一个 IP 一个 IP 添加)
```
route add -host 146.148.149.202 dev eno16777984
route add -host 146.148.149.202 gw 146.148.149.193
```
添加到网络的路由(批量)
```
route add -net 146.148.149.0 netmask 255.255.255.0 dev eno16777984
route add -net 146.148.149.0 netmask 255.255.255.0 gw 146.148.149.193
```
简洁写法
```
route add -net 146.148.150.0/24 dev eno16777984
route add -net 146.148.150.0/24 gw 146.148.150.193
```
添加默认网关
```
route add default gw 146.148.149.193
```
删除主机路由:
```
route del -host 146.148.149.202 dev eno16777984
```
删除网络路由:
```
route del -net 146.148.149.0 netmask 255.255.255.0
route del -net 146.148.150.0/24
```
删除默认路由
```
route del default gw 146.148.149.193
```
#### 在 linux 下设置永久路由的方法
服务器启动时自动设置路由,第一想到的可能时 `rc.local`
按照 linux 启动的顺序,rc.local 里面的内容是在 linux 所有服务都启动完毕,最后才被执行的,也就是说,这里面的内容是在 NFS 之后才被执行的,那也就是说在 NFS 启动的时候,服务器上的静态路由是没有被添加的,所以 NFS 挂载不能成功。
/etc/sysconfig/static-routes
```
any net 192.168.3.0/24 gw 192.168.3.254
any net 10.250.228.128 netmask 255.255.255.192 gw 10.250.228.129
```
使用 static-routes 的方法是最好的。无论重启系统和 service network restart 都会生效
static-routes 文件又是什么呢,这个是 network 脚本 (/etc/init.d/network) 调用的,大致的程序如下
```
if [ -f /etc/sysconfig/static-routes ]; then
grep "^any" /etc/sysconfig/static-routes | while read ignore args ; do
/sbin/route add -$args
done
fi
```
从这段脚本可以看到,这个就是添加静态路由的方法,static-routes 的写法是
any net 192.168.0.0/16 gw 网关 ip
### Microsoft windows 网络 \(samba\)
| 命令 | 说明 |
|--------|--------|
|\# mount \-t smbfs \-o username=user,password=pass //WinClient/share /mnt/share|挂载一个 windows 网络共享|
|\# nbtscan ip\_addr|netbios 名解析|
|\# nmblookup \-A ip\_addr|netbios 名解析|
|\# smbclient \-L ip\_addr/hostname|显示一台 windows 主机的远程共享|
|\# smbget \-Rr smb://ip\_addr/share|像 wget 一样能够通过 smb 从一台 windows 主机上下载文件|
### IPTABLES \(firewall\)
| 命令 | 说明 |
|--------|--------|
|\# iptables \-t filter \-L|显示过滤表的所有链路|
|\# iptables \-t nat \-L|显示 nat 表的所有链路|
|\# iptables \-t filter \-F|以过滤表为依据清理所有规则|
|\# iptables \-t nat \-F|以 nat 表为依据清理所有规则|
|\# iptables \-t filter \-X|删除所有由用户创建的链路|
|\# iptables \-t filter \-A INPUT \-p tcp \-\-dport telnet \-j ACCEPT|允许 telnet 接入|
|\# iptables \-t filter \-A OUTPUT \-p tcp \-\-dport http \-j DROP|阻止 HTTP 连出|
|\# iptables \-t filter \-A FORWARD \-p tcp \-\-dport pop3 \-j ACCEPT|允许转发链路上的 POP3 连接|
|\# iptables \-t filter \-A INPUT \-j LOG \-\-log\-prefix|记录所有链路中被查封的包|
|\# iptables \-t nat \-A POSTROUTING \-o eth0 \-j MASQUERADE|设置一个 PAT \(端口地址转换、) 在 eth0 掩盖发出包|
|\# iptables \-t nat \-A PREROUTING \-d 192\.168\.0\.1 \-p tcp \-m tcp \-\-dport 22 \-j DNAT \-\-to\-destination 10\.0\.0\.2:22|将发往一个主机地址的包转向到其他主机|
# Linux 简单管理
## ssh
### ssh 简介及基本操作
#### 简介
SSH,全名 secure shell,其目的是用来从终端与远程机器交互,SSH 设计之处初,遵循了如下原则:
* 机器之间通讯的内容必须经过加密。
* 加密过程中,通过 public key 加密,private 解密。
#### 密钥
SSH 通讯的双方各自持有一个公钥私钥对,公钥对对方是可见的,私钥仅持有者可见,你可以通过"ssh-keygen"生成自己的公私钥,默认情况下,公私钥的存放路径如下:
* 公钥:$HOME/.ssh/id_rsa.pub
* 私钥:$HOME/.ssh/id_rsa
#### 基于口令的安全验证通讯原理
建立通信通道的步骤如下:
```
1. 远程主机将公钥发给用户 ---- 远程主机收到用户的登录请求,把自己的公钥发给客户端,客户端检查这个 public key 是否在自己的 $HOME/.ssh/known_hosts 中,如果没有,客户端会提示是否要把这个 public key 加入到 known_hosts 中。
2. 用户使用公钥加密密码 ------ 用户使用这个公钥,将登录密码加密后,发送回来
3. 远程主机使用私钥加密 ------ 远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。
4. 客户端把 PUBLIC KEY(client), 发送给服务器。
5. 至此,到此为止双方彼此拥有了对方的公钥,开始双向加密解密。
```
PS:当网络中有另一台冒牌服务器冒充远程主机时,客户端的连接请求被服务器 B 拦截,服务器 B 将自己的公钥发送给客户端,客户端就会将密码加密后发送给冒牌服务器,冒牌服务器就可以拿自己的私钥获取到密码,然后为所欲为。因此当第一次链接远程主机时,在上述步骤中,会提示您当前远程主机的”公钥指纹”,以确认远程主机是否是正版的远程主机,如果选择继续后就可以输入密码进行登录了,当远程的主机接受以后,该台服务器的公钥就会保存到 ~/.ssh/known_hosts 文件中。
#### StrictHostKeyChecking 和 UserKnownHostsFile
> * 如何让连接新主机时,不进行公钥确认?
> * SSH 客户端的 StrictHostKeyChecking 配置指令,可以实现当第一次连接服务器时,自动接受新的公钥。
> * [例子:] ssh -o StrictHostKeyChecking=no 192.168.0.110
> * 如何防止远程主机公钥改变导致 SSH 连接失败
> * 将本地的 known_hosts 指向不同的文件,就不会造成公钥冲突导致的中断了,提示信息由公钥改变中断警告,变成了首次连接的提示。结合自动接收新的公钥
> * [例子:] ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null 192.168.0.110
### 基于密匙的安全验证通讯原理
这种方式你需要在当前用户家目录下为自己创建一对密匙,并把公匙放在需要登录的服务器上。当你要连接到服务器上时,客户端就会向服务器请求使用密匙进行安全验证。服务器收到请求之后,会在该服务器上你所请求登录的用户的家目录下寻找你的公匙,然后与你发送过来的公匙进行比较。如果两个密匙一致,服务器就用该公匙加密“质询”并把它发送给客户端。客户端收到“质询”之后用自己的私匙解密再把它发送给服务器。与第一种级别相比,第二种级别不需要在网络上传送口令。
PS:简单来说,就是将客户端的公钥放到服务器上,那么客户端就可以免密码登录服务器了,那么客户端的公钥应该放到服务器上哪个地方呢?默认为你要登录的用户的家目录下的 .ssh 目录下的 authorized_keys 文件中(即:~/.ssh/authorized_keys)。
我们的目标是:用户已经在主机 A 上登录为 a 用户,现在需要以不输入密码的方式以用户 b 登录到主机 B 上。
可以把密钥理解成一把钥匙,公钥理解成这把钥匙对应的锁头,
把锁头(公钥)放到想要控制的 server 上,锁住 server, 只有拥有钥匙(密钥)的人,
才能打开锁头,进入 server 并控制
而对于拥有这把钥匙的人,必需得知道钥匙本身的密码,才能使用这把钥匙(除非这把钥匙没设置密码), 这样就可以防止钥匙被了配了(私钥被人复制)
当然,这种例子只是方便理解罢了,
拥有 root 密码的人当然是不会被锁住的,而且不一定只有一把锁(公钥),
但如果任何一把锁,被人用其对应的钥匙(私钥)打开了,server 就可以被那个人控制了
所以说,只要你曾经知道 server 的 root 密码,并将有 root 身份的公钥放到上面,
就可以用这个公钥对应的私钥"打开" server, 再以 root 的身分登录,即使现在 root 密码已经更改!
步骤如下:
1. 以用户 a 登录到主机 A 上,生成一对公私钥。
2. 把主机 A 的公钥复制到主机 B 的 authorized_keys 中,可能需要输入 b@B 的密码。
ssh-copy-id -i ~/.ssh/id_dsa.pub b@B
3. 在 a@A 上以免密码方式登录到 b@B
ssh b@B
tips:
假如 B 机器修改端口后,将主机 A 上的公钥复制到 B 机的操作方法是(下面方法中双引号是必须的):
ssh-copy-id "-p 端口号 b@B"
### SSH 端口转发
SSH 还同时提供了一个非常有用的功能,这就是端口转发。它能够将其他 TCP 端口的网络数据通过 SSH 链接来转发,并且自动提供了相应的加密及解密服务。这一过程有时也被叫做“隧道”(tunneling),这是因为 SSH 为其他 TCP 链接提供了一个安全的通道来进行传输而得名。
SSH 端口转发自然需要 SSH 连接,而 SSH 连接是有方向的,从 SSH Client 到 SSH Server 。
而我们所要访问的应用也是有方向的,应用连接的方向也是从应用的 Client 端连接到应用的 Server 端。
比如需要我们要访问 Internet 上的 Web 站点时,Http 应用的方向就是从我们自己这台主机 (Client) 到远处的 Web Server。
> * SSH 连接和应用的连接这两个连接的方向一致,那我们就说它是本地转发。
> * SSH 连接和应用的连接这两个连接的方向不同,那我们就说它是远程转发。
#### SSH 正向连接
正向连接就是 client 连上 server,然后把 server 能访问的机器地址和端口(当然也包括 server 自己)镜像到 client 的端口上。
```
何时使用本地 Tunnel?
> * 比如说你在本地访问不了某个网络服务(如 www.google.com),而有一台机器(如:xx.xx.xx.xx) 可以,那么你就可以通过这台机器的 ssh 服务来转发
```
使用方法
```
ssh -L <local port>:<remote host>:<remote port> <SSH hostname>
ssh -L [客户端 IP 或省略]:[客户端端口]:[服务器能访问的 IP]:[服务器能访问的 IP 的端口] [登陆服务器的用户名 @服务器 IP] -p [服务器 ssh 服务端口(默认 22)]
```
ssh -L 1433:target_server:1433 user@ssh_host
***windows 下使用本地转发 xshell***
```
(1)ssh 远程连接到 Linux
(2) 打开代理设置面板,点击:view -> Tunneling Pane, 在弹出的窗口选择 Forwarding Rules
(3) 在空白处右键:add。
在弹出的 Forwarding Rule,
Type 选择"Local(Outgoing)";
Source Host 使用默认的 localhost; Listen Port 添上端口 8888;
Destination Host 使用默认的 localhost;Destination Port 添上 80;
Destination Host 设置为 localhost 为要访问的机器,可以设置为登陆后的机器可以访问到的 IP
```
#### SSH 反向连接
反向连接就是 client 连上 server,然后把 client 能访问的机器地址和端口(也包括 client 自己)镜像到 server 的端口上。
```
何时使用反向连接?
比如当你下班回家后就访问不了公司内网的机器了,遇到这种情况可以事先在公司内网的机器上执行远程 Tunnel,连上一台公司外网的机器,等你下班回家后就可以通过公司外网的机器去访问公司内网的机器了。
```
使用方法
```
ssh -R <remote port>:<local host>:<local port> <SSH hostname>
ssh -R [服务器 IP 或省略]:[服务器端口]:[客户端能访问的 IP]:[客户端能访问的 IP 的端口] [登陆服务器的用户名 @服务器 IP] -p [服务器 ssh 服务端口(默认 22)]
```
**外网机器 A 要控制 内网机器 B**
A 主机:外网,ip:122.122.122.122,sshd 端口:2222(默认是 22)
B 主机:内网,sshd 端口:2222(默认是 22)
无论是外网主机 A,还是内网主机 B 都需要跑 ssh daemon
***首先在内网机器 B 上执行***
```
ssh -NfR 1234:localhost:2222 user1@122.122.122.122 -p 2222
```
这句话的意思是将 A 主机的 1234 端口和 B 主机的 2222 端口绑定,相当于远程端口映射 (Remote Port Forwarding)。
***外网机器 A 会 listen 本地 1234 端口***
```
---- 外网机器 A sshd 会 listen 本地 1234 端口
#netstat -tanp | grep sshd
#Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
#tcp 0 0 127.0.0.1:1234 0.0.0.0:* LISTEN 4234/sshd
---- 在外网机器 A 登录内网机器 B(非 root 用户的话,直接 user@localhost 即可)
#ssh user@localhost -p1234
```
#### SSH 反向连接自动重连
上面的反向连接(Reverse Connection)不稳定,可能随时断开,需要内网主机 B 再次向外网 A 发起连接,这时需要个"朋友"帮你在内网 B 主机执行这条命令。可以使用 Autossh 或者 while 进行循环。
(1) 在 B 机器上将 B 机器公钥放到外网机器 A 上(实现 B 机器自动登录到 A 机器)
(2) 用 Autossh 或者 while 循环 保持 ssh 反向隧道一直连接,CentOS 需要使用 epel 源下载
在 CentOS6 和 CentOS7 都可以执行下面的命令安装 epel 仓库
**while**
编写脚本写入如下内容
```
#!/bin/bash
# 远程机器的 IP 和端口
remote_ip=122.122.122.122
remote_port=2222
while [[ 1==1 ]]
do
ssh -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -N -R:1234:localhost:22 -p ${remote_port} root@${remote_ip}
sleep 3
done
```
执行脚本后,即可以通过登陆 122.122.122.122 机器访问本地 1234 端口进行访问此机器
注;可以将此脚本放在后台中运行,并加到系统自启动程序中
**autossh**
```
#yum -y install epel-release
```
安装号 autossh 后使用如下方法进行反向连接
```
#autossh -M 5678 -NfR 1234:localhost:2222 user1@122.122.122.122 -p2222
```
比之前的命令添加的一个 -M 5678 参数,负责通过 5678 端口监视连接状态,连接有问题时就会自动重连
### windows 下 xshell 使用
* 私钥,在 Xshell 里也叫用户密钥
* 公钥,在 Xshell 里也叫主机密钥
利用 xshell 密钥管理服务器远程登录,
(1)Xshell 自带有用户密钥生成向导:点击菜单栏的工具 ->新建用户密钥生成向导
(2) 添加公钥 (Pubic Key) 到远程 Linux 服务器
## 用户管理
### Linux 踢出其他正在 SSH 登陆用户
***查看系统在线用户***
```
[root@Linux ~]#w
20:40:18 up 1 day, 23 min, 4 users, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root tty1 - Fri09 10:10m 0.34s 0.34s -bash
root pts/2 192.168.31.124 10:30 4:39m 0.99s 0.99s -bash
root pts/3 192.168.31.124 19:55 0.00s 0.07s 0.00s w
root pts/4 192.168.31.124 19:55 4:52 0.16s 0.16s -bash
```
***查看当前自己占用终端***
```
[root@Linux ~]# who am i
root pts/4 2016-10-30 19:55 (192.168.31.124)
```
***用 pkill 命令剔除对方***
```
[root@Linux ~]# pkill -kill -t pts/2
[root@Linux ~]# w
20:44:15 up 1 day, 27 min, 3 users, load average: 0.01, 0.03, 0.01
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
root tty1 - Fri09 10:14m 0.34s 0.34s -bash
root pts/3 192.168.31.124 19:55 51.00s 1.43s 1.35s vim base.md
root pts/4 192.168.31.124 19:55 0.00s 0.21s 0.00s w
```
如果最后查看还是没有干掉,建议加上 -9 强制杀死。
[root@Linux ~]# pkill -9 -t pts/2
### 使用脚本创建有 sudo 权限的用户
```
cat >./create_user.sh <<-'EOF'
#!/bin/bash
arg="ceshi"
if id ${arg}
then
echo "the username is exsit!"
else
useradd $arg
echo "ceshi_password" | passwd --stdin $arg
echo "${arg} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/${arg}
fi
EOF
```
以上脚本会创建用户 `ceshi` 同时用户的密码为 `ceshi_password` ,并且此用户有 sudo 权限
### 无交互式修改用户密码
```
echo "123456" | passwd --stdin root
```
## 网卡 bond
Linux 多网卡绑定
网卡绑定 mode 共有七种 (0~6) bond0、bond1、bond2、bond3、bond4、bond5、bond6
常用的有三种
> * mode=0:平衡负载模式,有自动备援,但需要”Switch”支援及设定。
> * mode=1:自动备援模式,其中一条线若断线,其他线路将会自动备援。
> * mode=6:平衡负载模式,有自动备援,不必”Switch”支援及设定。
需要说明的是如果想做成 mode 0 的负载均衡,仅仅设置这里 options bond0 miimon=100 mode=0 是不够的,与网卡相连的交换机必须做特殊配置(这两个端口应该采取聚合方式),因为做 bonding 的这两块网卡是使用同一个 MAC 地址。从原理分析一下(bond 运行在 mode 0 下):
mode 0 下 bond 所绑定的网卡的 IP 都被修改成相同的 mac 地址,如果这些网卡都被接在同一个交换机,那么交换机的 arp 表里这个 mac 地址对应的端口就有多 个,那么交换机接受到发往这个 mac 地址的包应该往哪个端口转发呢?正常情况下 mac 地址是全球唯一的,一个 mac 地址对应多个端口肯定使交换机迷惑了。所以 mode0 下的 bond 如果连接到交换机,交换机这几个端口应该采取聚合方式(cisco 称为 ethernetchannel,foundry 称为 portgroup),因为交换机做了聚合后,聚合下的几个端口也被捆绑成一个 mac 地址。我们的解 决办法是,两个网卡接入不同的交换机即可。
mode6 模式下无需配置交换机,因为做 bonding 的这两块网卡是使用不同的 MAC 地址。
## 其他设置
### 时区及时间
时区就是时间区域,主要是为了克服时间上的混乱,统一各地时间。地球上共有 24 个时区,东西各 12 个时区(东 12 与西 12 合二为一)。
#### UTC 和 GMT
时区通常写成`+0800`,有时也写成`GMT +0800`,其实这两个是相同的概念。
GMT 是格林尼治标准时间(Greenwich Mean Time)。
UTC 是协调世界时间(Universal Time Coordinated),又叫世界标准时间,其实就是`0000`时区的时间。
#### Linux 下调整时区及更新时间
修改系统时间
```
$ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
```
修改 /etc/sysconfig/clock 文件,修改为:
```
ZONE="Asia/Shanghai"
UTC=false
ARC=false
```
校对时间
```
$ntpdate cn.ntp.org.cn
```
设置硬件时间和系统时间一致并校准
```
$/sbin/hwclock --systohc
```
***定时同步时间设置***
凌晨 5 点定时同步时间
```
echo "0 5 * * * /usr/sbin/ntpdate cn.ntp.org.cn" >> /var/spool/cron/root
或者
echo "0 5 * * * /usr/sbin/ntpdate 133.100.11.8" >> /var/spool/cron/root
```
### 登录提示信息
#### 修改登录前的提示信息
**(1) 系统级别的设置方法 /etc/issue**
使用此方法时远程 ssh 连接的时候并不会显示
```
在登录系统输入用户名之前,可以看到上方有 WELCOME...... 之类的信息,这里会显示 LINUX 发行版本名称,内核版本号,日期,机器信息等等信息,
首先打开 /etc/issue 文件,可以看到里面是这样一段"Welcome to <LINUX 发行版本名称》-kernel 后接各项参数、"
参数的各项说明:
\r 显示 KERNEL 内核版本号;
\l 显示虚拟控制台号;
\d 显示当前日期;
\n 显示主机名;
\m 显示机器类型,即 CPU 架构,如 i386 等;
可以显示所有必要的信息:
Welcome to <LINUX 发行版本名称》-kernel \r (\l) \d \n \m.
```
### 修改登录成功后的信息
motd(message of the day)
修改登录成功后的提示信息在此文件中添加内容即可:/etc/motd
如:
```
/////////////////////////////////////
系统初始化配置提示
xxxx
应用联系人:xxxx 联系方式:xxxx
/////////////////////////////////////
```
# CentOS 7 vs CentOS 6 的不同
## 运行相关
**桌面系统**
[CentOS6] GNOME 2.x
[CentOS7] GNOME 3.x(GNOME Shell)
**文件系统**
[CentOS6] ext4
[CentOS7] xfs
**内核版本**
[CentOS6] 2.6.x-x
[CentOS7] 3.10.x-x
**启动加载器**
[CentOS6] GRUB Legacy (+efibootmgr)
[CentOS7] GRUB2
**防火墙**
[CentOS6] iptables
[CentOS7] firewalld
**默认数据库**
[CentOS6] MySQL
[CentOS7] MariaDB
**文件结构**
[CentOS6] /bin, /sbin, /lib, and /lib64 在 / 下
[CentOS7] /bin, /sbin, /lib, and /lib64 移到 /usr 下
**主机名**
[CentOS6] /etc/sysconfig/network # 修改主机名时,修改此文件,同时在命令行执行 "hostname 新主机名"
[CentOS7] /etc/hostname
**时间同步**
[CentOS6]
$ ntp
$ ntpq -p
[CentOS7]
$ chrony
$ chronyc sources
**修改时区**
[CentOS6]
$ vim /etc/sysconfig/clock
ZONE="Asia/Shanghai"
UTC=fales
$ sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
[CentOS7]
$ timedatectl set-timezone Asia/Shanghai
$ timedatectl status
**修改语言**
[CentOS6]
$ vim /etc/sysconfig/i18n
LANG="en_US.utf8"
$ /etc/sysconfig/i18n
$ locale
[CentOS7]
$ localectl set-locale LANG=en_US.utf8
$ localectl status
**重启关闭**
1) 关闭
[CentOS6]
$ shutdown -h now
[CentOS7]
$ poweroff
$ systemctl poweroff
2) 重启
[CentOS6]
$ reboot
$ shutdown -r now
[CentOS7]
$ reboot
$ systemctl reboot
3) 单用户模式
[CentOS6]
$ init S
[CentOS7]
$ systemctl rescue
4) 启动模式
[CentOS6]
[GUICUI]
$ vim /etc/inittab
id:3:initdefault:
[CUIGUI]
$ startx
[CentOS7]
[GUICUI]
$ systemctl isolate multi-user.target
[CUIGUI]
$systemctl isolate graphical.target
默认
$ systemctl set-default graphical.target
[CentOS6]
$ chkconfig service_name on/off
[CentOS7]
$ systemctl enable service_name
$ systemctl disable service_name
**服务一览**
[CentOS6]
$ chkconfig --list
[CentOS7]
$ systemctl list-unit-files
$ systemctl --type service
**强制停止**
[CentOS6]
$ kill -9 <PID>
[CentOS7]
$ systemctl kill --signal=9 sshd
## 网络
**网络信息**
[CentOS6]
$ netstat
$ netstat -I
$ netstat -n
[CentOS7]
$ ip -s l
$ ss
**IP 地址 MAC 地址**
[CentOS6]
$ ifconfig -a
[CentOS7]
$ ip address show
**路由**
[CentOS6]
$ route -n
$ route -A inet6 -n
[CentOS7]
$ ip route show
$ ip -6 route show
================================================
FILE: doc/Linux/op.md
================================================
# 常用问题处理
<!-- vim-markdown-toc GFM -->
* [1 系统配置](#1-系统配置)
* [1.1 Yum 安装安装包时提示证书过期](#11-yum-安装安装包时提示证书过期)
* [1.2 系统日志中的时间不准确](#12-系统日志中的时间不准确)
* [1.3 Linux 系统日志出现 hung_task_timeout_secs 和 blocked for more than 120 seconds](#13--linux-系统日志出现-hung_task_timeout_secs-和-blocked-for-more-than-120-seconds)
* [问题现象](#问题现象)
* [问题原因](#问题原因)
* [处理方法](#处理方法)
* [内核参数解释](#内核参数解释)
* [2 磁盘](#2-磁盘)
* [2.1 lvm 变为 inactive 状态](#21-lvm-变为-inactive-状态)
<!-- vim-markdown-toc -->
## 1 系统配置
### 1.1 Yum 安装安装包时提示证书过期
yum 安装安装包时提示"Peer's Certificate has expired"
https 的证书是有开始时间和失效时间的。因此本地时间要在这个证书的有效时间内。不过最好的方式,还是能够把时间进行同步。
```
# ntpdate pool.ntp.org
```
### 1.2 系统日志中的时间不准确
重启下 rsyslog 服务
```
/etc/init.d/rsyslog restart
```
### 1.3 Linux 系统日志出现 hung_task_timeout_secs 和 blocked for more than 120 seconds
#### 问题现象
Linux 系统出现系统没有响应。 在 /var/log/message 日志中出现大量的类似如下错误信息:
```
echo 0 > /proc/sys/kernel/hung_task_timeout_secs disables this message.
blocked for more than 120 seconds
```
同时看监控时发现,服务器异常期间磁盘 io 比较高,cpu load 比较高
#### 问题原因
默认情况下, Linux 会最多使用 40% 的可用内存作为文件系统缓存。当超过这个阈值后,文件系统会把将缓存中的内存全部写入磁盘, 导致后续的 IO 请求都是同步的。
将缓存写入磁盘时,有一个默认 120 秒的超时时间。 出现上面的问题的原因是 IO 子系统的处理速度不够快,不能在 120 秒将缓存中的数据全部写入磁盘。
IO 系统响应缓慢,导致越来越多的请求堆积,最终系统内存全部被占用,导致系统失去响应。
#### 处理方法
根据应用程序情况,对 vm.dirty_ratio,vm.dirty_background_ratio 两个参数进行调优设置。 例如,推荐如下设置:
```
# sysctl -w vm.dirty_ratio=10
# sysctl -w vm.dirty_background_ratio=5
# sysctl -p
```
如果系统永久生效,修改 /etc/sysctl.conf 文件。加入如下两行:
```
#vi /etc/sysctl.conf
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
```
重启系统生效。
#### 内核参数解释
> * vm.dirty_background_ratio: 这个参数指定了当文件系统缓存脏页数量达到系统内存百分之多少时(如 5%)就会触发 pdflush/flush/kdmflush 等后台回写进程运行,将一定缓存的脏页异步地刷入外存;
> * vm.dirty_ratio: 而这个参数则指定了当文件系统缓存脏页数量达到系统内存百分之多少时(如 10%),系统不得不开始处理缓存脏页(因为此时脏页数量已经比较多,为了避免数据丢失需要将一定脏页刷入外存);在此过程中很多应用进程可能会因为系统转而处理文件 IO 而阻塞。
一般情况下,dirty_ratio 的触发条件不会达到,因为每次会先达到 vm.dirty_background_ratio 的条件,然后触发 flush 进程进行异步的回写操作,但是这一过程中应用进程仍然可以进行写操作,如果应用进程写入的量大于 flush 进程刷出的量,就会达到 vm.dirty_ratio 这个参数所设定的坎,此时操作系统会转入同步地处理脏页的过程,阻塞应用进程。
## 2 磁盘
### 2.1 lvm 变为 inactive 状态
lvscan 查看 lvm 状态
```
[root@DB01 log]# lvscan
ACTIVE '/dev/OraBack/backupone' [7.00 TB] inherit
ACTIVE '/dev/OraBack/backuptwo' [7.00 TB] inherit
ACTIVE '/dev/OraBack/backupthree' [1.00 TB] inherit
inactive '/dev/OraBack【vg 名字】/orcl' [3.00 TB] inherit
```
激活 VG
```
[root@DB01 log]# vgchange -ay OraBack
4 logical volume(s) in volume group "OraBack" now active
```
lvscan 查看 lvm 状态
```
[root@DB01 log]# lvscan
ACTIVE '/dev/OraBack/backupone' [7.00 TB] inherit
ACTIVE '/dev/OraBack/backuptwo' [7.00 TB] inherit
ACTIVE '/dev/OraBack/backupthree' [1.00 TB] inherit
ACTIVE '/dev/OraBack/orcl' [3.00 TB] inherit
```
挂载
mount -a
================================================
FILE: doc/Linux/optimize.md
================================================
# Linux 优化
<!-- vim-markdown-toc GFM -->
* [说明](#说明)
* [应用类型](#应用类型)
* [监测工具](#监测工具)
* [综合工具之 sar](#综合工具之-sar)
* [dstat](#dstat)
* [Linux 性能监测 CPU 篇](#linux-性能监测-cpu-篇)
* [底线](#底线)
* [vmstat 命令](#vmstat-命令)
* [mpstat 命令](#mpstat-命令)
* [ps 命令](#ps-命令)
* [Linux 性能监测 内存篇](#linux-性能监测-内存篇)
* [vmstat 命令](#vmstat-命令-1)
* [Linux 性能监测 磁盘 IO 篇](#linux-性能监测-磁盘-io-篇)
* [内存页](#内存页)
* [缺页中断](#缺页中断)
* [File Buffer Cache](#file-buffer-cache)
* [页面类型](#页面类型)
* [IO's Per Seconds(OIPS)](#ios-per-secondsoips)
* [顺序 IO 和随机 IO](#顺序-io-和随机-io)
* [SWAP](#swap)
* [关掉 swap](#关掉-swap)
* [Linux 性能监测 网络篇](#linux-性能监测-网络篇)
* [netperf](#netperf)
* [iperf](#iperf)
* [tcpdump 和 tcptrace](#tcpdump-和-tcptrace)
* [ulimit 关于系统连接数的优化](#ulimit-关于系统连接数的优化)
* [修改方式](#修改方式)
<!-- vim-markdown-toc -->
## 说明
系统优化是一项复杂、繁琐、长期的工作,优化前需要监测、采集、测试、评估,优化后也需要测试、采集、评估、监测,而且是一个长期和持续的过程,不是说现在又花了、测试了,以后就可以一劳永逸,而不是说书本上的优化就适合眼下正在运行的系统,不同的系统、不同的硬件、不用的应用优化的重点也不同、优化的方法也不同、优化的参数也不同。
性能监测是系统优化过程中重要的一环,如果没有监测、不清楚性能瓶颈在哪里,怎么优化呢?所以`找到性能瓶颈`是性能监测的目的,也是系统优化的关键。
系统由若干子系统构成,通常修改一个子系统有可能影响到另外一个子系统,甚至会导致整个系统不稳定、崩溃。
所以说优化、监测、测试通常是连在一起的,而且是一个循环而且长期的过程,通常监测的子系统有以下这些:
* CPU
* Memory
* IO
* Network
这些子系统互相依赖,了解这些子系统的特性,监测这些子系统的系能参数以及及时发现可能会出现的瓶颈对系统优化很有帮助
本系列将按照 CPU、内存、磁盘 IO、网络这几个方面分别介绍
### 应用类型
不同的系统用途也不同,要找到性能瓶颈需要知道系统跑的是什么应用、有些什么特点,比如 web server 对系统的要求肯定和 file server 不一样,所以分清不同系统的应用类型很重要。
通常应用可以分为两种类型:
* IO 相关
* IO 相关的应用通常用来出来大量的数据,需要大量内存和存储,频繁 IO 操作读写数据
* 而对 CPU 的要求则较少,大部分时间 CPU 都在等待硬盘,比如,数据库服务器、文件服务器等
* CPU 相关
* CPU 相关的应用需要使用大量 CPU
* 比如高并发的 web/mail 服务器、图像 / 视频处理、科学计算等都可视作 CPU 相关的应用
### 监测工具
我们只需要简单的工具就可以对 Linux 的性能进行监控,以下常用的工具:
| 工具 | 简介 | 备注|
|:-:|---|---|
| top | 查看进程活动状态以及一些系统状况 | |
| vmstat | 查看系统状态、硬件和系统信息等 | |
| iostat | 查看 CPU 负载、硬盘状况 | |
| sar | 综合工具,查看系统状况 |(厂内默认安装)|
| mpstat | 查看多处理器状况 | |
| netstat | 查看网络状况|日常工作中推荐使用 ss 命令以替代 netstat |
| iptraf | 实时网络状态监测|【推荐】 比如网卡打满时,查看哪个 port 流量比较高 |
| tcpdump | 抓取网络数据包,详细分析 | |
| tcptrace | 网络包分析工具 | |
| netperf | 网络带宽工具 | |
| dstat | 综合了 vmstat、iostat、ifstat、netstat 等多个信息 |(python) 厂内默认安装|
#### 综合工具之 sar
> 安装及简介
```
yum instal -y sysstat
sysstat 工具包中包含两类工具:
即时查看工具:iostat、mpstat、sar
累计统计工具:sar
也就是说,sar 具有这两种功能。因此,sar 是 sysstat 中的核心工具。
为了实现 sar 的累计统计,系统必须周期地记录当时的信息,这是通过调用 /usr/lib64/sa/ 中的三个工具实现的:
(1) sa1 :收集并存储每天系统动态信息到一个二进制的文件中,用作 sadc 的前端程序
(2) sa2 :收集每天的系统活跃信息写入总结性的报告,用作 sar 的前端程序
(3) sadc :系统动态数据收集工具,收集的数据被写入一个二进制的文件中,它被用作 sar 工具的后端
在 CentOS 系统的默认设置中,以如下的方式使用这三个工具:
在守护进程 /etc/rc.d/init.d/sysstat 中使用 /usr/lib/sa/sadc -F -L - 命令创建当日记录文件,文件为 /var/log/sa/saDD,其中 DD 为当天的日期。当系统重新启动后,会向文件 /var/log/sa/saDD 输出类似 11:37:16 AM LINUX RESTART 这样的行信息。
在 cron 任务 /etc/cron.d/sysstat 中每隔 10 分钟执行一次 /usr/lib/sa/sa1 1 1 命令,将信息写入文件 /var/log/sa/saDD
在 cron 任务 /etc/cron.d/sysstat 中每天 23:53 执行一次 /usr/lib/sa/sa2 -A 命令,将当天的汇总信息写入文件 /var/log/sa/saDD
您可以修改 /etc/cron.d/sysstat 以适合您的需要。
另外,文件 /var/log/sa/saDD 为二进制文件,不能使用 more、less 等文本工具查看,必须用 sar 或 sadf 命令查看。
```
> 使用
```
sar -n DEV 网卡流量
sar -q 系统负载
sar -b 磁盘读写
sar -f /var/log/sa/saxx 历史文件
sar 每十分钟把系统的状态过滤一遍,并生产日志在 ls /var/log/sa
sar -n DEV 1 10 查看网卡流量(没 1s 输出一次,总共输出 10 次,最后会将这十次的采集信息进行统计)
rxpck/s 接收到的数据包 如果你要接收很多数据包,证明有人给你发送数据包 就是被攻击 几千是正常的,上万就不正常了
txpck/s 发送的数据包
rxkB/s 数据量
rxcmp/S 数据量
sar -n DEV -f /var/log/sa/sa17 查看网卡流量并指定一个文件
sar -q 1 10 查看系统负载
sar -q -f /var/log/sa/sa17 查看当月 16 号的数据
```
> 过期日志自动清理
```
保留天数配置:/etc/sysconfig/sysstat
执行文件 :/usr/lib64/sa/sa2
定时任务配置:/etc/cron.d/sysstat
```
如果没有定时过期清理,可以检查下定时任务中 "53 23 * * * root /usr/lib64/sa/sa2 -A" 是否为注释状态
#### dstat
> 常用命令
```
监控 CPU\MEN\磁盘IO 使用最多的进程: dstat --top-mem --top-io --top-cpu
dstat -c --top-cpu -d --top-bio --top-latency --disk-util
```
> * dstat --top-cpu:显示最消耗 CPU 的进程
> * dstat --top-cuptime:最消耗 CPU 时间的进程,以毫秒为单位
> * dstat --top-io:显示消耗 io 最多的进程
> * dstat --top-latency:显示哪个进程有最大的延迟
> * dstat --top-mem:显示用内存最多的线程
> * dstat --top-mem --top-cpu:俩个一起使用也是OK的
## Linux 性能监测 CPU 篇
CPU 的占用主要取决于什么样的资源在 CPU 上面运行,比如拷贝一个文件通常占用较少的 CPU,因为大部分工作是由 DMA(Direct Memory Access)完成,只是在完成拷贝以后给一个中断让 CPU 知道拷贝已经完成;科学计算通常占用较多的 CPU,大部分计算工作都需要在 CPU 上完成,内存、硬盘等子系统只是做暂时的数据存储工作。
要想监测和理解 CPU 的性能需要知道一些的操作系统基本知识,比如:中断、进程调度、进程上下文切换、可运行队列等。
用一个例子来简单介绍一下这些概念和他们的关系,CPU 很无辜,是个任劳任怨的打工仔,每时每刻都有工作在做(进程、线程)并且自己有一张工作清单(可运行队列),由老板(进程调度)来决定他该干什么,他需要和老板沟通以便得到老板的想法并及时调整自己的工作(上下文切换),部分工作做完以后还需要及时向老板汇报(中断),所以打工仔(CPU)除了做自己该做的工作之外,还有大量时间和精力花在沟通和汇报上。
CPU 也是一种硬件资源,和任何其他设备一样也需要驱动和管理程序才能使用,我们可以把内核的进程调度看作是 CPU 的管理程序,用来管理和分配 CPU 资源,合理安排进程抢占 CPU,并决定哪个进程该使用 CPU、哪个进程该等待。
操作系统内核里的进程调度主要用来调度两类资源:进程(或线程)和中断,进程调度给不同的资源分配了不同的优先级,**优先级最高的是硬件中断,其次是内核(系统)进程,最后是用户进程**。
每个 CPU 都维护这一个可运行队列,用来存放那些可运行的线程。线程要么在睡眠状态(blocked 正在等待 IO)、要么在可运行状态,如果 CPU 当前负载太高而新的请求不断,就会出现进程调度暂时应付不过来的情况,这个时候就不得不把线程暂时放到可运行队列中。
本文是讨论的性能监测,上面淡了一堆都没提到性能,那么这些概念和性能监测有什么关系呢?关系重大!如果你是老板,你如何检查打工仔的效率(性能)呢?我们一般会通过以下这些信息来判断打工仔是否偷懒:
* 打工仔接受和完成多少任务并向老板汇报了(中断)
* 打工仔和老板沟通、写上每项工作的工作进度(上下文切换)
* 打工仔的工作列表是不是都有排满(可运行队列)
* 打工仔工作效率如何,是不是在偷懒(CPU 利用率)
现在把打工仔换成 CPU,我们可以通过查看这些重要参数:**中断**、**上下文切换**、**可运行队列**、**CPU 利用率**来检测 CPU 的性能。
### 底线
Linux 性能监测:介绍提到了性能监测需要知道底线,那么监测 CPU 性能的底线是什么呢?
通常我们期望我们的系统能达到以下目标:
* **CPU 利用率**,如果 CPU 用 100% 的利用率,那么应该达到这样一个平衡:65%-70% User Time,30%-35% System Time,0%-5% Idle Time
* **上下文切换**,上下文切换应该和 CPU 利用率联系起来看,如果能保持上面的 CPU 利用率平衡,大量的上下文切换是可以接受的
* **可运行队列**,每个可运行队列不应该由超过 1-3 个线程(每处理器),比如:双处理器系统的可运行队列里不应该超过 6 个线程
### vmstat 命令
vmstat 是个查看系统整体性能的小工具,小巧,即使在很 heavy 的情况下也允许良好,并且可以用时间间隔采集得到连续的性能数据。
参数介绍:
* r,可运行队列的线程数,这些线程都是可运行状态,只不过 CPU 暂时不可用
* b,被 blocked 的进程数,正在等待 IO 请求
* in,被处理过的中断数
* cs,系统上正在做上下文切换的数目
* us,用户占用 CPU 的百分比
* sys,内核和中断占用 CPU 的百分比
* wa,所有可运行的线程被 blocked 以后都在等待 IO,这时候 CPU 空闲的百分比
* id,CPU 完全空闲的百分比
举两个现实中的例子来分析一下
```
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
4 0 140 2915476 341288 3951700 0 0 0 0 1057 523 19 81 0 0 0
4 0 140 2915724 341296 3951700 0 0 0 0 1048 546 19 81 0 0 0
4 0 140 2915848 341296 3951700 0 0 0 0 1044 514 18 82 0 0 0
4 0 140 2915848 341296 3951700 0 0 0 24 1044 564 20 80 0 0 0
4 0 140 2915848 341296 3951700 0 0 0 0 1060 546 18 82 0 0 0
```
从上面的数据可以看出几点:
1. interrupts(in) 非常高,context switch(cs) 比较低,说明这个 CPU 一直在不停的请求资源
2. user time(us) 一直保持在 80% 以上,而且上下文切换较低 (cs),说明某个进程可能一直霸占着 CPU
* run queue(r) 刚好在 4 个
```
$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
14 0 140 2904316 341912 3952308 0 0 0 460 1106 9593 36 64 1 0 0
17 0 140 2903492 341912 3951780 0 0 0 0 1037 9614 35 65 1 0 0
20 0 140 2902016 341912 3952000 0 0 0 0 1046 9739 35 64 1 0 0
17 0 140 2903904 341912 3951888 0 0 0 76 1044 9879 37 63 0 0 0
16 0 140 2904580 341912 3952108 0 0 0 0 1055 9808 34 65 1 0 0
```
从上面的数据可以看出几点:
1. context switch(cs) 比 interrupts(in) 要高的多,说明内核不得不来回切换进程
2. 进一步观察发现 system time(sy) 很高而 user time(us) 很低,而且加上高频度的上下文切换 (cs),说明正在运行的应用程序调用了大量的系统调用
3. run queue(r) 在 14 个线程以上,按照这个而是机器的硬件配置 (4 核),应该保持在 12 以内
### mpstat 命令
mpstat 和 vmstat 类似,不同的是 mpstat 可以输出多个处理器的数据,下面的输出显示 CPU1 和 CPU2 基本上没有派上用场,系统有足够的能力处理更多的任务
```
$ mpstat -P ALL 1
Linux 2.6.18-164.el5 (vpsee) 11/13/2009
02:24:33 PM CPU %user %nice %sys %iowait %irq %soft %steal %idle intr/s
02:24:34 PM all 5.26 0.00 4.01 25.06 0.00 0.00 0.00 65.66 1446.00
02:24:34 PM 0 7.00 0.00 8.00 0.00 0.00 0.00 0.00 85.00 1001.00
02:24:34 PM 1 13.00 0.00 8.00 0.00 0.00 0.00 0.00 79.00 444.00
02:24:34 PM 2 0.00 0.00 0.00 100.00 0.00 0.00 0.00 0.00 0.00
02:24:34 PM 3 0.99 0.00 0.99 0.00 0.00 0.00 0.00 98.02 0.00
```
### ps 命令
如何查看某个程序、进程占用了多少 CPU 资源呢?下面是 java 在一台 Linux 服务器上的运行情况,当前只有 2 个 java 进程
```
$ while :; do ps -eo pid,ni,pri,pcpu,psr,comm | grep 'java'; sleep 1; done
PID NI PRI %CPU PSR COMMAND
7252 0 24 3.2 3 java
9846 0 24 8.8 0 java
7252 0 24 3.2 2 java
9846 0 24 8.8 0 java
7252 0 24 3.2 2 java
```
## Linux 性能监测 内存篇
这里讲到的`内存`包括**物理内存**和**虚拟内存**。虚拟内存 (Virtual Memory) 把计算机的内存空间扩展到硬盘,物理内存 (RAM) 和硬盘的一部分空间 (SWAP) 组合在一起作为虚拟内存为计算机提供了一个连续的虚拟内存空间,好处是我们拥有的内存`变多了`,可以运行更多、更大的程序,坏处是把部分硬盘当内存用,整体性能受到影响,硬盘读写速度要比内存慢几个数量级,并且 RAM 和 SWAP 之间的交换增加了系统的负担。
在操作系统里,虚拟内存被分为页,在 x86 系统上每个页大小是 4KB。Linux 内核读写虚拟内存是以“页”为单位操作的,把内存转移到硬盘交换空间 (SWAP) 和从交换空间读取内存的时候都是按页来读写的。
内存和 SWAP 的这种交互过程称为页面交换 (Paging),值得注意的是 paging 和 swapping 是两个完全不同的概念,国内很多参考书把这两个概念混为一谈,swapping 也翻译为交换,在操作系统里是指把某程序完全交换到硬盘以腾出内存给新程序使用,和 paging 只交换程序的部分(页面)是两个不同的概念。春吹的 swapping 在现代操作系统中已经很难看到了,因为把整个程序交换到硬盘的办法既耗时又费力而且没必要,现代操作系统基本都是 paging 或者 paging/swapping 混合,swapping 最初是在 Unix system V 上实现的。
虚拟内存管理是 Linux 内核里面最复杂的部分,要弄懂这部分内容可能需要一本书的讲解。这里只介绍和性能监测有关的两个内核进程:kswapd 和 pdflush。
**kswapd daemon**用来检查 pages_high 和 pages_low,如果可用内存少于 pages_low,kswapd 就开始扫描并试图释放 32 个页面,并且重复扫描释放的过程知道可用内存大于 pages_high 为止。扫描的时候检查 3 件事:
* 如果页面没有修改,把页放到可用内存列表里
* 如果页面被文件系统修改,把页面内容写到磁盘上
* 如果页面被修改了,但不是被文件系统修改的,把页面写到交换空间
**pdflush daemon**用来同步文件相关的内存页面,把内存页面及时同步到硬盘上。比如打开一个文件,文件被导入到内存里,对文件修改并保存后,内核并不马上保存文件到硬盘,由 pdfush 决定什么时候把相应页面写到硬盘,这由一个内核参数 vm.dirty_background_ratio 来控制,比如下面的参数显示脏页面(dirty pages)达到所有内存页面 10% 的时候开始写入硬盘。
```
# /sbin/sysctl -n vm.dirty_background_ratio
10
```
### vmstat 命令
继续 vmstat 一些参数的介绍,上一篇 Linux 性能监测:CPU 介绍了 vmstat 的部分参数,这里介绍另外一部分。以下数据来自一个 256MB RAM,512MB SWAP 的 Xen VPS:
```
# vmstat 1
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 3 252696 2432 268 7148 3604 2368 3608 2372 288 288 0 0 21 78 1
0 2 253484 2216 228 7104 5368 2976 5372 3036 930 519 0 0 0 100 0
0 1 259252 2616 128 6148 19784 18712 19784 18712 3821 1853 0 1 3 95 1
1 2 260008 2188 144 6824 11824 2584 12664 2584 1347 1174 14 0 0 86 0
2 1 262140 2964 128 5852 24912 17304 24952 17304 4737 2341 86 10 0 0 4
```
*memory*
* swpd,已使用的 SWAP 控件大小,KB 为单位
* free,可用的物理内存大小,KB 为单位
* buff,物理内存用来缓存读写操作的 buffer 大小,KB 为单位
* cache,物理内存用来缓存进程地址空间的 cache 大小,KB 为单位
*swap*
* si,数据从 SWAP 读取到 RAM(swap in)的大小,KB 为单位
* so,数据从 RAM 写到 SWAP(swap in)的大小,KB 为单位
*io*
* bi,磁盘块从文件系统或 SWAP 读取到 RAM(blocks in)的大小,block 为单位
* bo,磁盘块从 RAM 写到文件系统或 SWAP(blocks out)的大小,block 为单位
上面是一个频繁读写交换区的例子,可以观察到以下几点:
* 物理可用内存 free 基本没有显著变化,swapd 逐步增加,说明最小可用的内存使用保持在 256MB X 10% = 2.56MB 左右,当脏数据达到 10% 的时候 (vm.dirty_background_ratio = 10) 就开始大量使用 swap
* buff 稳步减少说明系统知道内存不够用了,kwapd 正在从 buff 那里借用部分内存
* kswapd 持续把脏数据写到 swap 交换区 (so),并且从 swapd 主键增加看出确实如此。根据上面将的 kswapd 扫描时检查的三件事,如果页面被修改了,但不是被文件系统修改的,把页面写到 swap,所以这里 swapd 持续增加
## Linux 性能监测 磁盘 IO 篇
磁盘通常是计算机最慢的子系统,也是最容易出现性能瓶颈的地方,因为磁盘离 CPU 最远而且 CPU 访问磁盘涉及到机械操作,比如转轴、寻轨等,访问硬盘和访问内存之间的速度差别是以数量级来计算的,就像 1 天和 1 分钟的差别一样,要监测 IO 性能,有必要了解一下基本原理和 Linux 是如何处理硬盘和内存之间的 IO 的。
### 内存页
Memory 介绍中提到了内存和硬盘之间的 IO 是以页为单位来进行的,在 Linux 系统上 1 页的大小为 4K。可以用下面命令查看系统默认的页面大小:
```
$getconf PAGESIZE
...
4096
...
```
### 缺页中断
Linux 利用虚拟内存极大的扩展了程序地址空间,是的原来物理内存不能容下的程序也可以通过内存和硬盘之间的不断交换(把暂时不用的内存页交换到硬盘,把需要的内存页从硬盘读到内存)来赢得更多的内存,看起来就像物理内存被扩大一样。
事实上这个过程对程序是完全透明的,程序完全不用理会自己哪一部分、什么时候被交换到内存,一切都在内核的虚拟内存管理来完成。
当程序启动的时候,Linux 内核首先检查 CPU 的缓存和物理内存,如果数据已经在内存里就忽略,如果数据不再内存里就引起一个**缺页中断(Page Fault)**,然后从硬盘读取缺页,并把缺页缓存到物理内存中。
缺页中断可分为主缺页中断(Major Page Fault)和次缺页中断(Minor Page Fault),要从磁盘读取数据而产生的中断是主缺页中断;数据已经读到内存并被缓存起来,从内存缓存区中而不是直接从硬盘中读取数据而产生的中断是次缺页中断。
上面的内存缓存区起到了预读硬盘的作用,内核现在物理内存里寻找缺页,没有的话产生次缺页中断从内存缓存中找,如果还没有发现的话就从硬盘读取。很显然,把多于的内存拿出来做成内存缓存区有助于提高访问速度。
这里还有一个**命中率**的问题,运气好的话如果每次缺页都能从内存缓存区读取的话将会极大提升性能。要提升命中率的一个简单的方法就是增大内存缓存区面积,缓存区越大预存的页面就越多,命中率也会越多。
下面的 time 命令可以用来查看某程序第一次启动的时候产生了多少主缺页中断和次缺页中断:
```
$ /usr/bin/time -v date
...
Major (requiring I/O) page faults: 1
Minor (reclaiming a frame) page faults: 260
...
```
### File Buffer Cache
从上面的内存缓存区(也叫文件缓存区 File Buffer Cache) 读取页比从硬盘读取页要快的多,所以 Linux 内核希望能尽可能产生次却也中断(从文件缓存区读),并且能尽可能避免主缺页中断(从硬盘读),这样随着次缺页中断的增多,文件缓存区也逐步增大,直到系统只有少量可用物理内存的时候 Linux 才开始释放不用的页。
我们运行 Linux 一段时间后会发现虽然系统上运行的程序不多,但是可用内存总是很少,这样给大家造成了 Linux 对内存管理很低效的假象,事实上 Linux 把哪些暂时不用的物理内存高效的利用起来做预存(内存缓存区)呢。下面打印的是一台 Sun 服务器上的物理内存和文件缓存区的情况:
```
$ cat /proc/meminfo
MemTotal: 8182776 kB
MemFree: 3053808 kB
Buffers: 342704 kB
Cached: 3972748 kB
```
这台服务器总共有 8GB 物理内存(MemTotal),3GB 左右可用内存(MemFree),343MB 左右用来做磁盘缓存(Buffers),4GB 左右用来做文件缓存区(Cached),可见 Linux 真的用了很多物理内存做 Cache,而且这个缓存区还可以不断增长。
### 页面类型
Linux 中内存页面有三种类型:
* Read Pages,只读页(或代码页),那些通过主缺页中断从硬盘读取的页面,包括不能修改的静态文件、可执行文件、库文件等。当内核需要它们的时候把它们读到内存中,当内存不足的时候,内核就释放它们到空闲列表,当程序再次需要它们的时候需要通过缺页中断再次读到内存
* Dirty Pages,脏页,指那些在内存中被修改过的数据页,比如文本文件等。这些文件有 pdflush 负责同步到硬盘,内存不足的时候由 kswapd 和 pdflush 把数据写回硬盘并释放内存
* Anonymous Pages,匿名页,那些属于某个进程但是又和任何文件无关联,不能被同步到硬盘上,内存不足的时候有 kswapd 负责将它们写到交换分区并释放内存
### IO's Per Seconds(OIPS)
每次磁盘 IO 请求都需要一定的时间,和访问内存比起来这个等待时间简直难以忍受。
在一台 2001 年典型 1GHz PC 上,磁盘随机访问一个 word 需要 8000000 nanosec = 8 millisec,顺序访问一个 word 需要 200nanosec;而从内存访问一个 word 只需要 10 nanoses。(数据来自:Teach Yourself Programming in Ten Years)这个硬盘可以提供 125 次 IOPS(1000 ms / 8 ms)。
IOPS:每秒 IO 的次数。
### 顺序 IO 和随机 IO
IO 分为顺序 IO 和随机 IO 两种,性能监测前需要弄清楚系统偏向顺序 IO 的应用还是随机 IO 的应用。
随机 IO 是指同时顺序请求大量数据,比如数据库执行大量的查询、流媒体服务等,顺序 IO 可以同时很快的移动大量数据。可以这样来评估 IOPS 的性能,用每秒读写 IO 字节数除以每秒读写 IOPS 数,rkB/s 除以 r/s,wkB/s 除以 w/s。下面显示的是连续两秒的 IO 情况,可见每次 IO 写的数据是增加的(45060.00 / 99.00 = 455.15 KB per IO,54272.00 / 112.00 = 484.57 KB per IO)。
相对随机 IO 而言,顺序 IO 更应该重视每次 IO 的吞吐能力(KB per IO):
```
$ iostat -kx 1
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 2.50 25.25 0.00 72.25
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
sdb 24.00 19995.00 29.00 99.00 4228.00 45060.00 770.12 45.01 539.65 7.80 99.80
avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 1.00 30.67 0.00 68.33
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
sdb 3.00 12235.00 3.00 112.00 768.00 54272.00 957.22 144.85 576.44 8.70 100.10
```
随机 IO 是指随机请求数据,其 IO 速度不依赖于数据的大小和排序,依赖于磁盘的每秒能 IO 的次数,比如 Web 服务、Mial 服务等每次请求的数据都很小,随机 IO 每次同时会有更多的请求数产生,所以磁盘的每秒能 IO 多少次是关键
```
$ iostat -kx 1
avg-cpu: %user %nice %system %iowait %steal %idle
1.75 0.00 0.75 0.25 0.00 97.26
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
sdb 0.00 52.00 0.00 57.00 0.00 436.00 15.30 0.03 0.54 0.23 1.30
avg-cpu: %user %nice %system %iowait %steal %idle
1.75 0.00 0.75 0.25 0.00 97.24
Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await svctm %util
sdb 0.00 56.44 0.00 66.34 0.00 491.09 14.81 0.04 0.54 0.19 1.29
```
按照上面的公式得出:436.00 / 57.00 = 7.65KB per IO, 491.09 / 66.34 = 7.40 KB per IO ,与顺序 IO 比较发现,随机 IO 的 KB per IO 小到可以忽略不计,可见对于随机 IO 而言重要的是每秒能 IOPS 的次数,而不是每次 IO 的吞吐能力(KB per IO)
### SWAP
当系统没有足够物理内存来应付所有请求的时候就会用到 swap 设备,swap 设备可以是一个文件,也可以是磁盘分区。
不过要小心的是,使用 swap 的代价非常大。如果系统没有物理内存可用,就会频繁 swapping,如果 swap 设备和程序正在访问的数据在同一个文件系统上,那会碰到严重的 IO 问题,最终导致整个系统迟缓,甚至崩溃。
swap 设备和内存之间的 swapping 状况是判断 Linux 系统性能的重要参考,我们已经有很多工具可以用来监测 swap 和 swapping 的情况,比如:top、cat/proc/meminfo、vmstat 等:
```
$ cat /proc/meminfo
MemTotal: 8182776 kB
MemFree: 2125476 kB
Buffers: 347952 kB
Cached: 4892024 kB
SwapCached: 112 kB
...
SwapTotal: 4096564 kB
SwapFree: 4096424 kB
...
```
#### 关掉 swap
```
(1) 将 /etc/fstab 文件中所有设置为 swap 的设备关闭
[root@meetbill ~]# swapoff -a
(2) 设置开机不启动 swap
将 /etc/fstab 中 swap 行注释掉
```
## Linux 性能监测 网络篇
网络的监测是所有 Linux 子系统里面最复杂的,有太多的因素在里面,比如:延迟、阻塞、冲突、丢包等,更糟的是与 Linux 主机相连的路由器、交换机、无线信号都会影响到整体网络并且很难判断是因为 Linux 网络子系统的问题还是别的设备的问题,增加了监测和判断的复杂度。
现在我们使用的所有网卡都称为自适应网卡,意思是说能根据网络上的不同网络设备导致的不同网络速度和工作模式进行自动调整。我们可以通过 ethtool 共苦;来查看网卡的配置和工作模式:
```
# /sbin/ethtool eth0
Settings for eth0:
Supported ports: [ TP ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Half 1000baseT/Full
Supports auto-negotiation: Yes
Advertised link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Half 1000baseT/Full
Advertised auto-negotiation: Yes
Speed: 100Mb/s
Duplex: Full
Port: Twisted Pair
PHYAD: 1
Transceiver: internal
Auto-negotiation: on
Supports Wake-on: g
Wake-on: g
Current message level: 0x000000ff (255)
Link detected: yes
```
上面给出的例子说明网卡有 10baseT,100baseT 和 1000baseT 三种选择,目前正在自适应为 100baseT(Speed:100MB/s)。可以通过 ethtool 工具强制网卡工作在 1000basseT 下:
```
# /sbin/ethtool -s eth0 speed 1000 duplex full autoneg off
iptraf
```
两台主机之间有网线(或无线)、路由器、交换机等设备,测试两台主机之间的网络性能的一个办法就是在这两个系统之间互发数据并统计结果,看看吞吐量、延迟、速率如何。
iptraf 就是一个很好的查看本机网络吞吐量的好工具,支持文字图形界面,很直观。下面图片显示在 100mbps 速率的网络下这个 Linux 系统的发送传输率有点慢,Outgoing rates 只有 66mbps:
```
# iptraf -d eth0
```
### netperf
netperf 运行在 client/server 模式下,比 iptraf 能更多样化的测试终端的吞吐量。先在服务器端启动 netserver:
```
# netserver
Starting netserver at port 12865
Starting netserver at hostname 0.0.0.0 port 12865 and family AF_UNSPEC
```
然后在客户端测试服务器,执行一次持续 10 秒的 TCP 测试:
```
# netperf -H 172.16.38.36 -l 10
TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 172.16.38.36 (172.16.38.36) port 0 AF_INET
Recv Send Send
Socket Socket Message Elapsed
Size Size Size Time Throughput
bytes bytes bytes secs. 10^6bits/sec
87380 16384 16384 10.32 93.68
```
从上面输出可以看出,网络吞吐量在 94mbps 左右,对于 100mbps 的网络来说这个性能算的上很不错。
上面的测试是在服务器和客户端位于同一个局域网,并且局域网是有线网的情况,你也可以试试不同结构、不同速率的网络,比如:网络之间中间多个路由器、客户端在 wi-fi、VPN 等情况。
netperf 还可以通过建立一个 TCP 连接并顺序地发送数据包来测试每秒有多少 TCP 请求和响应。下面的输出显示在 TCP requests 使用 2K 大小,responses 使用 32K 的情况下处理速率为每秒 243:
```
# netperf -t TCP_RR -H 172.16.38.36 -l 10 -- -r 2048,32768
TCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 172.16.38.36 (172.16.38.36) port 0 AF_INET
Local /Remote
Socket Size Request Resp. Elapsed Trans.
Send Recv Size Size Time Rate
bytes Bytes bytes bytes secs. per sec
16384 87380 2048 32768 10.00 243.03
16384 87380
```
同时可以使用 netperf 持续发送数据包,通过 atop 查看网卡流量查看网络状态是否良好
### iperf
iperf 和 netperf 运行方式类似,也是 server/client 模式,现在服务器端启动 iperf:
```
# iperf -s -D
------------------------------------------------------------
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------
Running Iperf Server as a daemon
The Iperf daemon process ID : 5695
```
然后在客户端对服务器进行测试,客户端线连接到服务器端(172.16.38.36),并在 30 秒内每隔 5 秒对服务器和客户端之间的网络进行一次带宽测试和采样:
```
# iperf -c 172.16.38.36 -t 30 -i 5
------------------------------------------------------------
Client connecting to 172.16.38.36, TCP port 5001
TCP window size: 16.0 KByte (default)
------------------------------------------------------------
[ 3] local 172.16.39.100 port 49515 connected with 172.16.38.36 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0- 5.0 sec 58.8 MBytes 98.6 Mbits/sec
[ ID] Interval Transfer Bandwidth
[ 3] 5.0-10.0 sec 55.0 MBytes 92.3 Mbits/sec
[ ID] Interval Transfer Bandwidth
[ 3] 10.0-15.0 sec 55.1 MBytes 92.4 Mbits/sec
[ ID] Interval Transfer Bandwidth
[ 3] 15.0-20.0 sec 55.9 MBytes 93.8 Mbits/sec
[ ID] Interval Transfer Bandwidth
[ 3] 20.0-25.0 sec 55.4 MBytes 92.9 Mbits/sec
[ ID] Interval Transfer Bandwidth
[ 3] 25.0-30.0 sec 55.3 MBytes 92.8 Mbits/sec
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-30.0 sec 335 MBytes 93.7 Mbits/sec
```
### tcpdump 和 tcptrace
tcpdump 和 tcptrace 提供了一种更细致的分析方法,先用 tcpdump 按要求捕获数据包把结果输出到某一文件,然后再用 tcptrace 分析其文件格式。这个工具组合可以提供一些难以用其他工具发现的信息:
```
# /usr/sbin/tcpdump -w network.dmp
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
511942 packets captured
511942 packets received by filter
0 packets dropped by kernel
# tcptrace network.dmp
1 arg remaining, starting with 'network.dmp'
Ostermann's tcptrace -- version 6.6.7 -- Thu Nov 4, 2004
511677 packets seen, 511487 TCP packets traced
elapsed wallclock time: 0:00:00.510291, 1002714 pkts/sec analyzed
trace file elapsed time: 0:02:35.836372
TCP connection info:
1: zaber:54581 - boulder:111 (a2b) 6> 5< (complete)
2: zaber:833 - boulder:32774 (c2d) 6> 5< (complete)
3: zaber:pcanywherestat - 172.16.39.5:53086 (e2f) 2> 3<
4: zaber:716 - boulder:2049 (g2h) 347> 257<
5: 172.16.39.100:58029 - zaber:12865 (i2j) 7> 5< (complete)
6: 172.16.39.100:47592 - zaber:36814 (k2l) 255380> 255378< (reset)
7: breakpoint:45510 - zaber:7012 (m2n) 9> 5< (complete)
8: zaber:35813 - boulder:111 (o2p) 6> 5< (complete)
9: zaber:837 - boulder:32774 (q2r) 6> 5< (complete)
10: breakpoint:45511 - zaber:7012 (s2t) 9> 5< (complete)
11: zaber:59362 - boulder:111 (u2v) 6> 5< (complete)
12: zaber:841 - boulder:32774 (w2x) 6> 5< (complete)
13: breakpoint:45512 - zaber:7012 (y2z) 9> 5< (complete)
```
tcptrace 功能很强大,还可以通过过滤和布尔表达式来找出有问题的连接,比如,找出转播大于 100segments 的连接:
```
# tcptrace -f'rexmit_segs>100' network.dmp
```
如果发现连接 #10 有问题,可以查看关于这个连接的其他信息:
```
# tcptrace -o10 network.dmp
```
下面的命令使用 tcptrace 的 slice 模式,程序自动在当前目录创建了一个 slice.dat 文件,这个文件包含了每隔 15 秒的转播信息:
```
# tcptrace -xslice network.dmp
# cat slice.dat
date segs bytes rexsegs rexbytes new active
--------------- -------- -------- -------- -------- -------- --------
16:58:50.244708 85055 4513418 0 0 6 6
16:59:05.244708 110921 5882896 0 0 0 2
16:59:20.244708 126107 6697827 0 0 1 3
16:59:35.244708 151719 8043597 0 0 0 2
16:59:50.244708 37296 1980557 0 0 0 3
17:00:05.244708 67 8828 0 0 2 3
17:00:20.244708 149 22053 0 0 1 2
17:00:35.244708 30 4080 0 0 0 1
17:00:50.244708 39 5688 0 0 0 1
17:01:05.244708 67 8828 0 0 2 3
17:01:11.081080 37 4121 0 0 1 3
```
## ulimit 关于系统连接数的优化
linux 默认值 open files 和 max user processes 为 1024
\#ulimit -n
1024
\#ulimit –u
1024
问题描述: 说明 server 只允许同时打开 1024 个文件,处理 1024 个用户进程
使用 ulimit -a 可以查看当前系统的所有限制值,使用 ulimit -n 可以查看当前的最大打开文件数。
新装的 linux 默认只有 1024 ,当作负载较大的服务器时,很容易遇到 error: too many open files 。因此,需要将其改大。
解决方法:
使用 ulimit –n 65535 可即时修改,但重启后就无效了。(注 ulimit -SHn 65535 等效 ulimit -n 65535 ,-S 指 soft ,-H 指 hard)
### 修改方式
有如下三种修改方式:
1. 在 /etc/rc.local 中增加一行 ulimit -SHn 65535
2. 在 /etc/profile 中增加一行 ulimit -SHn 65535
3. 在 /etc/security/limits.conf 最后增加:
```
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
```
具体使用哪种,在 CentOS 中使用第 1 种方式无效果,使用第 3 种方式有效果,而在 Debian 中使用第 2 种有效果
\# ulimit -n
65535
\# ulimit -u
65535
备注:ulimit 命令本身就有分软硬设置,加 -H 就是硬,加 -S 就是软默认显示的是软限制
soft 限制指的是当前系统生效的设置值。 hard 限制值可以被普通用户降低。但是不能增加。 soft 限制不能设置的比 hard 限制更高。 只有 root 用户才能够增加 hard 限制值。
```bash
#!/bin/bash
file=/etc/security/limits.conf
if grep '^* soft nofile' $file > /dev/null ;then
sed -i 's/^* soft nofile.*/* soft nofile 1024000/' $file
else
echo '* soft nofile 1024000' >> $file
fi
if grep '^* hard nofile' $file > /dev/null ;then
sed -i 's/^* hard nofile.*/* hard nofile 1024000/' $file
else
echo '* hard nofile 1024000' >> $file
fi
ulimit -SHn 1024000
```
================================================
FILE: doc/Linux/safety.md
================================================
## Linux 安全
<!-- vim-markdown-toc GFM -->
* [1 禁止 ping](#1-禁止-ping)
* [2 禁止密码登陆](#2-禁止密码登陆)
* [3 ssh 防暴力破解及提高 ssh 安全](#3-ssh-防暴力破解及提高-ssh-安全)
* [4 运维操作审计](#4-运维操作审计)
* [5 双因子认证](#5-双因子认证)
* [5.1 安装及配置篇](#51--安装及配置篇)
* [5.1.1 环境](#511-环境)
* [5.1.2 查看系统时间](#512-查看系统时间)
* [5.1.3 安装 google authenticator](#513-安装-google-authenticator)
* [5.1.4 为 SSH 服务器用 Google 认证器](#514-为-ssh-服务器用-google-认证器)
* [5.1.5 生成验证密钥](#515-生成验证密钥)
* [5.2 使用](#52-使用)
* [5.2.1 在安卓设备上运行 Google 认证器](#521-在安卓设备上运行-google-认证器)
* [5.2.2 终端使用二次身份验证登陆](#522-终端使用二次身份验证登陆)
* [5.3 常见问题及注意点](#53-常见问题及注意点)
* [5.3.1 登陆失败](#531-登陆失败)
* [5.3.2 是否可以不同的用户使用不用密钥](#532-是否可以不同的用户使用不用密钥)
* [5.3.3 是否可以使用 ssh 密钥直接登陆](#533-是否可以使用-ssh-密钥直接登陆)
* [5.4 原理](#54-原理)
* [5.4.1 前世今生](#541-前世今生)
* [5.4.2 TOTP 中的特殊问题](#542-totp-中的特殊问题)
* [6 iptables 命令](#6-iptables-命令)
* [6.1 iptables 是什么](#61-iptables-是什么)
* [6.2 iptables 示例](#62-iptables-示例)
* [6.2.1 filter 表 INPUT 链](#621-filter-表-input-链)
* [6.2.2 filter 表 OUTPUT 链](#622-filter-表-output-链)
* [6.2.3 filter 表的 FORWARD 链](#623-filter-表的-forward-链)
* [6.3 nat 表](#63-nat-表)
* [6.3.1 nat 表 PREROUTING 链](#631-nat-表-prerouting-链)
* [6.3.2 nat 表 POSTROUTING 链](#632-nat-表-postrouting-链)
* [6.3.3 nat 表做 HA 的实例](#633-nat-表做-ha-的实例)
* [6.3.4 nat 表为虚拟机做内外网联通](#634-nat-表为虚拟机做内外网联通)
* [6.4 iptables 管理命令](#64-iptables-管理命令)
* [6.4.1 查看 iptables 规则](#641-查看-iptables-规则)
* [6.4.2 清除 iptables 规则](#642-清除-iptables-规则)
* [6.4.3 保存 iptables 规则](#643-保存-iptables-规则)
* [6.5 常用操作](#65-常用操作)
* [6.5.1 使用 ip6tables 禁用 ipv6](#651-使用-ip6tables-禁用-ipv6)
* [6.5.2 配置 iptables 允许部分端口通行,其他全部阻止](#652-配置-iptables-允许部分端口通行其他全部阻止)
* [6.5.3 关闭某个端口外部访问](#653-关闭某个端口外部访问)
<!-- vim-markdown-toc -->
## 1 禁止 ping
禁止系统响应任何从外部 / 内部来的 ping 请求攻击者一般首先通过 ping 命令检测此主机或者 IP 是否处于活动状态 ,如果能够 ping 通某个主机或者 IP,那么攻击者就认为此系统处于活动状态,继而进行攻击或破坏。如果没有人能 ping 通机器并收到响应,那么就可以大大增强服务器的安全性,linux 下可以执行如下设置,禁止 ping 请求:
```
[root@localhost ~]#echo "1"> /proc/sys/net/ipv4/icmp_echo_ignore_all
```
默认情况下"icmp_echo_ignore_all"的值为"0",表示响应 ping 操作。
可以加上面的一行命令到 /etc/rc.d/rc.local 文件中,以使每次系统重启后自动运行
## 2 禁止密码登陆
## 3 ssh 防暴力破解及提高 ssh 安全
## 4 运维操作审计
[添加运维操作审计工具](https://github.com/meetbill/shell_menu)
## 5 双因子认证
海上生明月,天涯共此时!
### 5.1 安装及配置篇
#### 5.1.1 环境
server:CentOS 6.5/CentOS 7.3
#### 5.1.2 查看系统时间
使用外网机器时,创建的时候有可能不是北京时间
```
[root@centos ~]#date
Sun Aug 14 23:18:41 EDT 2011
[root@centos ~]# rm -rf /etc/localtime
[root@centos ~]# ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
```
#### 5.1.3 安装 google authenticator
安装 EPEL 源并安装 google_authenticator
```
#yum -y install epel-release
#yum -y install google-authenticator
```
#### 5.1.4 为 SSH 服务器用 Google 认证器
**配置 /etc/pam.d/sshd**
```
# CentOS 6.5 在"auth include password-auth"行前添加如下内容
# CentOS 7 在"auth substack password-auth"行前添加如下内容
auth required pam_google_authenticator.so
```
即先 google 方式认证再 linux 密码认证
**修改 SSH 服务配置 /etc/ssh/sshd_config**
ChallengeResponseAuthentication no->yes
```
sed -i 's#^ChallengeResponseAuthentication no#ChallengeResponseAuthentication yes#' /etc/ssh/sshd_config
```
**重启 SSH 服务**
```
# CentOS6
#service sshd restart
# CentOS7
# systemctl restart sshd
```
**关掉 selinux**
```
# setenforce 0
# 修改 /etc/selinux/config 文件 将 SELINUX=enforcing 改为 SELINUX=disabled
```
#### 5.1.5 生成验证密钥
在 Linux 主机上登陆需要认证的用户运行 Google 认证器(我这是使用 root 用户演示的)
```
$google-authenticator
```
直接一路输入 yes 即可,询问内容如下,想了解的可以看下
```
Do you want me to update your "~/.google_authenticator" file (y/n):y
应急码的保存路径
Do you want to disallow multiple uses of the same authentication token?
This restricts you to one login about every 30s,
but it increases your chances to notice or even prevent man-in-the-middle attacks (y/n)
是否禁止一个口令多用,自然也是答 y
By default, tokens are good for 30 seconds and in order to compensate for possible time-skew between the client and the server,
we allow an extra token before and after the current time. If you experience problems with poor time synchronization, you can increase the window from its default size of 1:30min to about 4min. Do you want to do so (y/n)
问是否打开时间容错以防止客户端与服务器时间相差太大导致认证失败。
这个可以根据实际情况来。如果一些 Android 平板电脑不怎么连网的,可以答 y 以防止时间错误导致认证失败。
If the computer that you are logging into isn't hardened against brute-force login attempts,
you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.Do you want to enable rate-limiting (y/n)
选择是否打开尝试次数限制(防止暴力攻击),自然答 y
```
这里需要记住的是
```
$cat ~/.google_authenticator 手机密钥和应急码保存路径
密钥
Your emergency scratch codes are: 一些生成的 5 个应急码,每个应急码只能使用一次
```
### 5.2 使用
#### 5.2.1 在安卓设备上运行 Google 认证器
***安装 google 身份验证器***
我的方法是在 UC 中搜索的 google 身份验证器进行的安装
***输入密钥***
选择"Enter provided key"选项,使用键盘输入账户名称和验证密钥
#### 5.2.2 终端使用二次身份验证登陆
***windows xshell***
打开 xshell(其他终端类似),选择登陆主机的属性。设置登陆方法为 Keyboard Interactive
登陆时输入用户名后,接着输入手机设备上的数字,然后输入密码
***linux***
linux 下直接输入
```
#ssh 用户名 @IP
```
连接比较慢时可以修改本机的客户端配置文件 ssh_config,注意,不是 sshd_config
GSSAPIAuthentication yes -->no
```
#sed -i 's#GSSAPIAuthentication yes#GSSAPIAuthentication no#' /etc/ssh/ssh_config
```
### 5.3 常见问题及注意点
#### 5.3.1 登陆失败
如果 SELinux 是打开状态,则会登陆失败,日志 /var/log/secret 中会有如下日志
```
Jan 3 23:42:50 hostname sshd(pam_google_authenticator)[1654]: Failed to update secret file "/home/username/.google_authenticator"
Jan 3 23:42:50 hostname sshd[1652]: error: PAM: Cannot make/remove an entry for the specified session for username from 192.168.0.5
```
#### 5.3.2 是否可以不同的用户使用不用密钥
可以,只需要在不同的用户执行`google-authenticator`即可
#### 5.3.3 是否可以使用 ssh 密钥直接登陆
可以,根据以上方法操作,只限制密码登陆时需要二次认证
### 5.4 原理
基于时间的一次性密码(Time-based One-time Password,简称 TOTP),只需要在手机上安装密码生成应用程序,就可以生成一个随着时间变化的一次性密码,用于帐户验证,而且这个应用程序不需要连接网络即可工作。仔细看了看这个方案的实现原理,发现挺有意思的。
#### 5.4.1 前世今生
***HOTP***
Google 的两步验证算法源自另一种名为 HMAC-Based One-Time Password 的算法,简称 HOTP。HOTP 的工作原理如下:
客户端和服务器事先协商好一个密钥 K,用于一次性密码的生成过程,此密钥不被任何第三方所知道。此外,客户端和服务器各有一个计数器 C,并且事先将计数值同步。
进行验证时,客户端对密钥和计数器的组合 (K,C) 使用 HMAC(Hash-based Message Authentication Code)算法计算一次性密码,公式如下:
> HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
上面采用了 HMAC-SHA-1,当然也可以使用 HMAC-MD5 等。HMAC 算法得出的值位数比较多,不方便用户输入,因此需要截断(Truncate)成为一组不太长十进制数(例如 6 位)。计算完成之后客户端计数器 C 计数值加 1。用户将这一组十进制数输入并且提交之后,服务器端同样的计算,并且与用户提交的数值比较,如果相同,则验证通过,服务器端将计数值 C 增加 1。如果不相同,则验证失败。
这里的一个比较有趣的问题是,如果验证失败或者客户端不小心多进行了一次生成密码操作,那么服务器和客户端之间的计数器 C 将不再同步,因此需要有一个重新同步(Resynchronization)的机制。
***TOTP***
介绍完了 HOTP,Time-based One-time Password(TOTP)也就容易理解了。TOTP 将 HOTP 中的计数器 C 用当前时间 T 来替代,于是就得到了随着时间变化的一次性密码。非常有趣吧!
一句话概括就是
> 海上升明月,天涯共此时!
#### 5.4.2 TOTP 中的特殊问题
***时间 T 的选取 (30 秒作为时间片)***
首先,时间 T 的值怎么选取?因为时间每时每刻都在变化,如果选择一个变化太快的 T(例如从某一时间点开始的秒数),那么用户来不及输入密码。如果选择一个变化太慢的 T(例如从某一时间点开始的小时数),那么第三方攻击者就有充足的时间去尝试所有可能的一次性密码(试想 6 位数字的一次性密码仅仅有 10^6 种组合),降低了密码的安全性。除此之外,变化太慢的 T 还会导致另一个问题。如果用户需要在短时间内两次登录账户,由于密码是一次性的不可重用,用户必须等到下一个一次性密码被生成时才能登录,这意味着最多需要等待 59 分 59 秒!这显然不可接受。综合以上考虑,
Google 选择了 30 秒作为时间片,T 的数值为从 Unix epoch(1970 年 1 月 1 日 00:00:00)来经历的 30 秒的个数。
***网络延时处理***
第二个问题是,由于网络延时,用户输入延迟等因素,可能当服务器端接收到一次性密码时,T 的数值已经改变,这样就会导致服务器计算的一次性密码值与用户输入的不同,验证失败。解决这个问题个一个方法是,服务器计算当前时间片以及前面的 n 个时间片内的一次性密码值,只要其中有一个与用户输入的密码相同,则验证通过。当然,n 不能太大,否则会降低安全性。
事实上,这个方法还有一个另外的功能。我们知道如果客户端和服务器的时钟有偏差,会造成与上面类似的问题,也就是客户端生成的密码和服务端生成的密码不一致。但是,如果服务器通过计算前 n 个时间片的密码并且成功验证之后,服务器就知道了客户端的时钟偏差。因此,下一次验证时,服务器就可以直接将偏差考虑在内进行计算,而不需要进行 n 次计算。
以上就是 Google 两步验证的工作原理,推荐大家使用,这确实是保护帐户安全的利器。
## 6 iptables 命令
### 6.1 iptables 是什么
iptables 是与 Linux 内核集成的 IP 信息包过滤系统,该系统有利于在 Linux 系统上更好地控制 IP 信息包过滤和防火墙配置。
### 6.2 iptables 示例
#### 6.2.1 filter 表 INPUT 链
怎么处理发往本机的包。
```
# iptables {-A|-D|-I} INPUT rule-specification
# iptables -A INPUT -s 10.1.2.11 -p tcp --dport 80 -j DROP
# iptables -A INPUT -s 10.1.2.11 -p tcp --dport 80 -j REJECT --reject-with tcp-reset
# iptables -A INPUT -s 10.1.2.11 -p tcp --dport 80 -j ACCEPT
```
以上表示将从源地址 10.1.2.11 访问本机 80 端口的包丢弃(以 tcp-reset 方式拒绝和接受)。
> * -s 表示源地址(--src,--source),其后面可以是一个 IP 地址(10.1.2.11)、一个 IP 网段(10.0.0.0/8)、几个 IP 或网段(192.168.1.11/32,10.0.0.0/8,添加完成之后其实是两条规则)。
> * -d 表示目的地址(--dst,--destination),其后面可以是一个 IP 地址(10.1.2.11)、一个 IP 网段(10.0.0.0/8)、几个 IP 或网段(10.1.2.11/32,10.1.3.0/24,添加完成之后其实是两条规则)。
> * -p 表示协议类型(--protocol),后面可以是 tcp, udp, udplite, icmp, esp, ah, sctp, all,其中 all 表示所有的协议。
> * --sport 表示源端口(--source-port),后面可以是一个端口(80)、一系列端口(80:90,从 80 到 90 之间的所有端口),一般在 OUTPUT 链使用。
> * --dport 表示目的端口(--destination-port),后面可以是一个端口(80)、一系列端口(80:90,从 80 到 90 之间的所有端口)。
> * -j 表示 iptables 规则的目标(--jump),即一个符合目标的数据包来了之后怎么去处理它。常用的有 ACCEPT, DROP, REJECT, REDIRECT, LOG, DNAT, SNAT。
> * (“就好象骗子给你打电话,ACCEPT 是接收,drop 就是直接拒收,reject 的话,相当于你还给骗子回个电话。”)
```
# iptables -A INPUT -p tcp --dport 80 -j DROP
# iptables -A INPUT -p tcp --dport 80:90 -j DROP
# iptables -A INPUT -m multiport -p tcp --dports 80,8080 -j DROP
```
以上表示将所有访问本机 80 端口(80 和 90 之间的所有端口,80 和 8080 端口)的包丢弃。
> * -m 匹配更多规则(--match),可以指定更多的 iptables 匹配扩展。可以是 tcp, udp, multiport, cpu, time, ttl 等,即你可以指定一个或多个端口,或者本机的一个 CPU 核心,或者某个时间段内的包。
#### 6.2.2 filter 表 OUTPUT 链
怎么处理本机向外发的包。
```
# iptables -A OUTPUT -p tcp --sport 80 -j DROP
```
以上这条规则意思是不允许访问本机 80 端口的包出去。即你可以向本机 80 端口发送请求包,但是本机回应给你的包会被该条规则丢弃。
INPUT 链与 OUTPUT 链用法一样,但是表示的意思不同。
#### 6.2.3 filter 表的 FORWARD 链
For packets being routed through the box(不知道怎么解释)。
其用法与 INPUT 链和 OUTPUT 链类似。
### 6.3 nat 表
nat 表有三条链,分别是 PREROUTING, OUTPUT, POSTROUTING。
#### 6.3.1 nat 表 PREROUTING 链
修改发往本机的包。
```
# iptables -t nat -A PREROUTING -p tcp -d 202.102.152.23 --dport 80 -j DNAT --to-destination 10.67.15.23:8080
# iptables -t nat -A PREROUTING -p tcp -d 202.102.152.23 -j DNAT --to-destination 10.67.15.23
```
以上这两条规则的意思是将发往 IP 地址 202.102.152.23 和端口 80 的包的目的地址修改为 10.67.15.23,目的端口修改为 8080。将发往 202.102.152.23 的其他非 80 端口的包目的地址修改为 10.67.15.23。第二条规则中的 -p tcp 是可选的,也可以指定其他协议。
其实类似这样的规则一般在路由器上做,路由器上有个公网 IP(202.102.152.23),其中有个用户的内网 IP(10.67.15.23)想提供外网的 web 服务,而路由器又不想将公网 IP 地址绑定到用户机器上,因此就出来了以上的蛋疼规则。
#### 6.3.2 nat 表 POSTROUTING 链
修改本机向外发的包。
```
# iptables -t nat -A POSTROUTING -p tcp -s 10.67.15.23 --sport 8080 -j SNAT --to-source 202.102.152.23:80
# iptables -t nat -A POSTROUTING -p tcp -s 10.67.15.23 -j SNAT --to-source 202.102.152.23
```
以上两条规则的意思是将从 IP 地址 10.67.15.23 和端口 8080 发出的包的源地址修改为 202.102.152.23,源端口修改为 80。将从 10.67.15.23 发出的非 80 端口的包的源地址修改为 202.102.152.23。
这两条正好与以上两条 PREROUTING 共同完成了内网用户想提供外网服务的功能。
其中的 --to-destination 和 --to-source 都可以缩写成 --to,在 DNAT 和 SNAT 中会分别被理解成 --to-destination 和 --to-source。
注: 之所以将内网服务的端口和外网服务的端口写的不一致是因为二者其实真的可以不一致。另外,是否将 PREROUTNG 中的 -d 改为域名就可以使用一个公网 IP 为不同用户提供服务了呢?这个需要哥哥我稍后验证。
#### 6.3.3 nat 表做 HA 的实例
有两台服务器和三个 IP 地址,分别是 10.1.2.21, 10.1.2.22, 10.1.5.11。假设他们提供的是相同的 WEB 服务,现在想让他们做 HA,而 10.1.5.11 是他们的 VIP。
* 10.1.2.21 这台的 NAT 规则如下:
```
# iptables -t nat -A PREROUTING -p tcp -d 10.1.2.11 --dport 80 -j DNAT --to-destination 10.1.2.21:80
# iptables -t nat -A POSTROUTING -p tcp -s 10.1.2.21 --sport 80 -j SNAT --to-source 10.1.2.11:80
```
* 10.1.2.22 这台的 NAT 规则如下:
```
# iptables -t nat -A PREROUTING -p tcp -d 10.1.2.11 --dport 80 -j DNAT --to-destination 10.1.2.22:80
# iptables -t nat -A POSTROUTING -p tcp -s 10.1.2.22 --sport 80 -j SNAT --to-source 10.1.2.11:80
```
默认可以认为 VIP 在 10.1.2.21 上挂着,那么当这台机器发生故障不能提供服务时,我们可以及时将 VIP 挂到 10.1.2.22 上,这样就可以保证服务不中断了。当然我们可以写一个简单的 SHELL 脚本来完成 VIP 的检测及挂载,方法非常简单。
注: LVS 的实现中貌似有这么一项,还没有深入去研究 LVS。
#### 6.3.4 nat 表为虚拟机做内外网联通
宿主机内网 IP 是 10.67.15.183(eth1),外网 IP 是 202.102.152.183(eth0),内网网关是 10.67.15.1,其上面的虚拟机 IP 是 10.67.15.250(eth1)。
目前虚拟机只能连接内网,其路由信息如下:
```
# ip r s
10.67.15.0/24 dev eth1 proto kernel scope link src 10.67.15.250
169.254.0.0/16 dev eth1 scope link metric 1003
192.168.0.0/16 via 10.67.15.1 dev eth1
172.16.0.0/12 via 10.67.15.1 dev eth1
10.0.0.0/8 via 10.67.15.1 dev eth1
default via 10.67.15.1 dev eth1
```
若要以 NAT 方式实现该虚拟机即能连接公网又能连接内网,则该虚拟机路由需要改成以下:
```
# ip r s
10.67.15.0/24 dev eth1 proto kernel scope link src 10.67.15.250
169.254.0.0/16 dev eth1 scope link metric 1003
192.168.0.0/16 via 10.67.15.1 dev eth1
172.16.0.0/12 via 10.67.15.1 dev eth1
10.0.0.0/8 via 10.67.15.1 dev eth1
default via 10.67.15.183 dev eth1
```
虚拟机连接内网的网关地址也可以写成宿主机内网 IP 地址。
宿主机上面添加如下 NAT 规则:
```
# iptables -t nat -A POSTROUTING -s 10.67.15.250/32 -d 10.0.0.0/8 -j SNAT --to-source 10.67.15.250
# iptables -t nat -A POSTROUTING -s 10.67.15.250/32 -d 172.16.0.0/12 -j SNAT --to-source 10.67.15.250
# iptables -t nat -A POSTROUTING -s 10.67.15.250/32 -d 192.168.0.0/16 -j SNAT --to-source 10.67.15.250
# iptables -t nat -A POSTROUTING -s 10.67.15.250/32 -j SNAT --to-source 202.102.152.183
```
以上四条规则的意思是将从源地址 10.67.15.250 发往内网机器上的数据包的源地址改为 10.67.15.250。将从源地址 10.67.15.250 发往公网机器上的数据包的源地址修改为 202.102.152.183。
### 6.4 iptables 管理命令
#### 6.4.1 查看 iptables 规则
```
# iptables -nL
# iptables -n -L
# iptables --numeric --list
# iptables -S
# iptables --list-rules
# iptables -t nat -nL
# iptables-save
```
> * -n 代表 --numeric,意思是 IP 和端口都以数字形式打印出来。否则会将 127.0.0.1:80 输出成 localhost:http。端口与服务的对应关系可以在 /etc/services 中查看。
> * -L 代表 --list,列出 iptables 规则,默认列出 filter 链中的规则,可以用 -t 来指定列出哪个表中的规则。
> * -t 代表 --tables,指定一个表。
> * -S 代表 --list-rules,以原命令格式列出规则。
iptables-save 命令是以原命令格式列出所有规则,可以 -t 指定某个表。
#### 6.4.2 清除 iptables 规则
```
# iptables -F
# iptables --flush
# iptables -F OUTPUT
# iptables -t nat -F
# iptables -t nat -F PREROUTING
```
> * -F 代表 --flush,清除规则,其后面可以跟着链名,默认是将指定表里所有的链规则都清除。
> * (警告:如果已经配置过默认规则为 deny 的环境,即 iptables -P INPUT DROP ,直接命令行执行 iptables -F 将使系统的所有网络访问中断,此坑已踩过)
#### 6.4.3 保存 iptables 规则
```
# /etc/init.d/iptables save
```
该命令会将 iptables 规则保存到 /etc/sysconfig/iptables 文件里面,如果 iptable 有开机启动的话,开机时会自动将这些规则添加到机器上。
### 6.5 常用操作
iptables 命令中的很多选项前面都可以加"!",意思是“非”。如"! -s 10.0.0.0/8"表示除这个网段以外的源地址,"! --dport 80"表示除 80 以外的其他端口。
#### 6.5.1 使用 ip6tables 禁用 ipv6
目前 ipv6 不禁用会存在安全隐患,那我们就可以通过 ip6tables 禁用 ipv6,我们只要在 ip6tables 的 filter 表上的出入口以及转发做限定就行了。
```
[root@meetbill ~]# vim /etc/sysconfig/ip6tables
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
# 添加这 3 条规则
-A INPUT -j REJECT --reject-with icmp6-adm-prohibited
-A FORWARD -j REJECT --reject-with icmp6-adm-prohibited
-A OUTPUT -j REJECT --reject-with icmp6-adm-prohibited
COMMIT
[root@meetbill ~]# /etc/init.d/ip6tables restart
```
可以通过 ifconfig 查看 ipv6 的地址
```
[root@meetbill ~]# ping6 -I eth0 fe80::20c:29ff:febc:8aab
```
#### 6.5.2 配置 iptables 允许部分端口通行,其他全部阻止
将下列内容放到脚本中,然后执行脚本即可,此脚本可以重复执行
```
#!/bin/bash
iptables -F /* 清除所有规则 */
iptables -A INPUT -p tcp --dport 22 -j ACCEPT /*允许包从 22 端口进入*/
iptables -A OUTPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT /*允许从 22 端口进入的包返回*/
iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT /*允许本机访问本机*/
iptables -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
iptables -A INPUT -p tcp -s 0/0 --dport 80 -j ACCEPT /*允许所有 IP 访问 80 端口*/
iptables -A OUTPUT -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP
#iptables-save > /etc/sysconfig/iptables /*保存配置*/
iptables -L /* 显示 iptables 列表 */
```
(警告:如果已经配置过默认规则为 deny 的环境,即 iptables -P INPUT DROP ,直接命令行执行 iptables -F 将使系统的所有网络访问中断,此坑已踩过)
如何清除配置尼(-P 为默认规则)
```
#!/bin/bash
iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT
iptables -F
#iptables-save > /etc/sysconfig/iptables
iptables -L
```
#### 6.5.3 关闭某个端口外部访问
场景: 设置 butterfly 服务外部机器无法访问,本机可正常访问
> 封禁命令
```
#iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT
#iptables -A INPUT -p tcp --dport 8585 -j DROP
```
> iptables -L INPUT --line-numbers
```
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 ACCEPT all -- localhost localhost
2 DROP tcp -- anywhere anywhere tcp dpt:8585
```
> 解封命令
```
iptables -D INPUT 2 (注意,这个 2 是行号,是iptables -L INPUT --line-numbers 所打印出来的行号)
iptables -D INPUT 1
```
================================================
FILE: doc/Linux/service.md
================================================
# 常见服务架设
<!-- vim-markdown-toc GFM -->
* [NTP](#ntp)
* [简介](#简介)
* [ntpd](#ntpd)
* [NTP Server 安装配置](#ntp-server-安装配置)
* [配置选项说明](#配置选项说明)
* [相关命令](#相关命令)
* [chrony](#chrony)
* [chrony server](#chrony-server)
* [chrony client](#chrony-client)
* [Cron](#cron)
* [Cron 基础](#cron-基础)
* [什么是 cron, crond, crontab](#什么是-cron-crond-crontab)
* [crontab 选项](#crontab-选项)
* [crontab 格式](#crontab-格式)
* [使用举例](#使用举例)
* [rsync](#rsync)
* [rsync 基本介绍](#rsync-基本介绍)
* [rsync 工作场景](#rsync-工作场景)
* [使用方法](#使用方法)
* [rsync 选项](#rsync-选项)
* [常用选项](#常用选项)
* [一些命令](#一些命令)
* [常用命令](#常用命令)
* [ssh 端口非默认 22 同步](#ssh-端口非默认-22-同步)
* [ssh 自动接受公钥和修改 known_hosts 文件](#ssh-自动接受公钥和修改-known_hosts-文件)
* [inotify+rsync 实现实时文件同步](#inotifyrsync-实现实时文件同步)
* [存储数据异地灾备](#存储数据异地灾备)
* [需求背景](#需求背景)
* [架构](#架构)
* [脚本内容](#脚本内容)
* [原理](#原理)
* [常见问题](#常见问题)
* [对大磁盘进行 inotify 监听时出错](#对大磁盘进行-inotify-监听时出错)
* [telnet-server](#telnet-server)
* [安装使用](#安装使用)
* [测试](#测试)
* [ftp](#ftp)
* [ftp 简介](#ftp-简介)
* [安装配置](#安装配置)
* [设置 FTP 虚拟账号密码](#设置-ftp-虚拟账号密码)
* [修改 proftpd 配置文件(早期系统镜像的默认 FTP 配置路径为 /etc/proftpd.conf)](#修改-proftpd-配置文件早期系统镜像的默认-ftp-配置路径为-etcproftpdconf)
* [获取 ftpasswd 用于设置虚拟账号](#获取-ftpasswd-用于设置虚拟账号)
* [设置账号密码](#设置账号密码)
* [删除 ftpasswd](#删除-ftpasswd)
* [重启 FTP 服务(如启动失败,proftpd -t -d5 检查配置文件出错点)](#重启-ftp-服务如启动失败proftpd--t--d5-检查配置文件出错点)
* [wget 使用](#wget-使用)
<!-- vim-markdown-toc -->
# NTP
## 简介
Network Time Protocol-NTP 是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS 等等)做同步化,它可以提供高精准度的时间校正(LAN 上与标准间差小于 1 毫秒,WAN 上几十毫秒),且可使用加密确认的方式来防止恶毒的协议攻击。默认使用 `UDP 123 端口`
NTP 提供准确时间,首先需要一个准确的 UTC 时间来源,NTP 获得 UTC 的时间来源可以从原子钟、天文台、卫星,也可从 Internet 上获取。时间服务器按照 NTP 服务器的等级传播,根据离外部 UTC 源的远近将所有服务器归入不用的层 (Stratum) 中。Stratum-1 在顶层由外部 UTC 接入,stratum-1 的时间服务器为整个系统的基础,Stratum 的总数限制在 15 以内。下图为 NTP 层次图:

## ntpd
### NTP Server 安装配置
关于 NTP 服务器的安装,根据不同版本安装方法也不同。REDHAT 系统则可以使用 yum 安装,Ubuntu 系列可以使用 `apt-get` 安装,这里不做具体的介绍,主要详细介绍配置文件的信息。
对于 CentOS 过滤注释和空行后,NTP 配置文件内容如下
```
# grep -vE '^#|^$' /etc/ntp.conf
driftfile /var/lib/ntp/drift
# 默认对所有 client 拒绝所有的操作
restrict default kod nomodify notrap nopeer noquery
restrict -6 default kod nomodify notrap nopeer noquery
# 允许本机地址的一切操作
restrict 127.0.0.1
restrict -6 ::1
# 允许其他机器连接
restrict default kod nomodify
server 0.centos.pool.ntp.org
server 1.centos.pool.ntp.org
server 2.centos.pool.ntp.org
includefile /etc/ntp/crypto/pw
keys /etc/ntp/keys
```
### 配置选项说明
* `driftfile` 选项, 用来保存系统时钟频率偏差。 ntpd 程序使用它来自动地补偿时钟的自然漂移, 从而使时钟即使在切断了外来时源的情况下, 仍能保持相当的准确度。`无需更改`
* `restrict` 语法为:restrict IP mask 掩码 参数
* IP 规定了允许或不允许访问的地址(此处若为 default,即为 0.0.0.0 所有 ip),配合掩码可以对某一网段进行限制。
* `ignore`: 关闭所有 NTP 服务
* `nomodiy`: 客户端不能修改服务端的时间,但可以作为客户端的校正服务器
* `notrust`: 拒绝没有通过认证的客户端
* `kod`: kod 技术科阻止 "Kiss of Death" 包(一种 DOS 攻击)对服务器的破坏
* `nopeer`: 不与其它同一层的 NTP 服务器进行同步
* `noquery`: 不提供时间查询,即用户端不能使用 ntpq,ntpc 等命令来查询 ntp 服务器
* `notrap`: 不提供 trap 远端事件登陆的功能
* `server [IP|FQDN|prefer]`指该服务器上层 NTP Server,使用 prefer 的优先级最高,没有使用 prefer 则按照配置文件顺序由高到低,默认情况下至少 15min 和上层 NTP 服务器进行时间校对
* `fudge`: 可以指定本地 NTP Server 层,如 `fudge 127.0.0.1 stratum 9`
* `broadcast 网段 子网掩码`: 指定 NTP 进行时间广播的网段,如`broadcast 192.168.1.255`
* `logfile`: 可以指定 NTP Server 日志文件
**bill 提醒**
```
restrict 用于权限控制,server 用于设定上级时间服务器
主要是这两个参数
```
几个与 NTP 相关的配置文件:` /usr/share/zoneinfo/`、`/etc/sysconfig/clock`、`/etc/localtime`
* `/usr/share/zoneinfo/`: 存放时区文件目录
* `/etc/sysconfig/clock`: 指定当前系统时区信息
* `/etc/localtime`: 相应的时区文件
如果需要修改当前时区,则可以从 /usr/share/zoneinfo/ 目录拷贝相应时区文件覆盖 /etc/localtime 并修改 /etc/sysconfig/clock 即可
```
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
sed -i 's:ZONE=.*:ZONE="Asia/Shanghai":g' /etc/sysconfig/clock
```
### 相关命令
`ntpstat` 查看同步状态
```
# ntpstat
synchronised to NTP server (192.168.0.18) at stratum 4
time correct to within 88 ms # 表面时间校正 88ms
polling server every 1024 s # 每隔 1024s 更新一次
```
`ntpq` 列出上层状态
```
# ntpq -np
remote refid st t when poll reach delay offset jitter
==============================================================
* NTPD(IP) IP 3 u 101 1024 377 14.268 0.998 0.143
```
输出说明:
* `remote`: NTP Server
* `refid` : 参考的上层 ntp 地址
* `st` : 层次
* `when` : 上次更新时间距离现在时常
* `poll` : 下次更新时间
* `reach` : 更新次数
* `delay` : 延迟
* `offset`: 时间补偿结果
* `jitter`: 与 BIOS 硬件时间差异
`ntpdate` 同步当前时间:`ntpdate NTP 服务器地址`
## chrony
使用安装命令安装 chrony 包即可
### chrony server
配置文件:/etc/chrony.conf
对于 chrony server 来说,主要配置两项,上游的 ntp 服务器和对下游的权限
> * 上游的 ntp 服务器
> * 有固定的 ntp 服务器或者可连互联网
> * 配置 `server 0.centos.pool.ntp.org iburst` 即可
> * 无外网环境使用本地的时间进行往下游同步
> * 配置 `local stratum 10`
> * 对下游的权限
> * `allow 10.0.0.0/24` 对 10.0.0 网段开放
> * `allow 0/0` 对所有 IP 开放
**bill 提醒**
```
(1) 无外网环境时,如果没有设置 local stratum 0,下游服务器显示的状态是不可达状态
(2) 不加 allow 记录时,默认拒绝所有连接
(3) chrony 端口为 udp 123
```
启动并设置开机自启
```
# systemctl enable chronyd.service
# systemctl start chronyd.service
```
### chrony client
对于 client 来说,只需要配置上游的服务器
配置文件:/etc/chrony.conf
添加 `server 上游服务器 IP/ 主机名 iburst`即可
启动并设置开机自启
```
# systemctl enable chronyd.service
# systemctl start chronyd.service
```
**查看同步状态**
```
#chronyc sources -v
```
上面命令会输出上游服务器的连接状态
> * `* 正常`
> * `? 不可达`
# Cron
## Cron 基础
### 什么是 cron, crond, crontab
> **cron** is the general name for the service that runs scheduled actions. **crond** is the name of the daemon that runs in the background and reads **crontab** files.
简单理解:cron 是服务,crond 是守护进程, crontab 的 crond 的配置文件。
### crontab 选项
+ `crontab -e` : Edit your crontab file, or create one if it doesn't already exist. # 推荐使用命令新增计划任务 -- 语法检查
+ `crontab -l` : Display your crontab file.
+ `crontab -r` : Remove your crontab file. # 慎用
+ `crontab -u user` : Used in conjunction with other options, this option allows you to modify or view the crontab file of user. When available, only administrators can use this option.
### crontab 格式
minute(s) hour(s) day(s) month(s) weekday(s) command(s)
```
# Use the hash sign to prefix a comment
# +—————- minute (0 – 59)
# | +————- hour (0 – 23)
# | | +———- day of month (1 – 31)
# | | | +——- month (1 – 12)
# | | | | +—- day of week (0 – 7) (Sunday=0 or 7)
# | | | | |
# * * * * * command to be executed
```
## 使用举例
使用命令 `crontab -e` 编辑 crontab 文件。
(1) 在每天的 7 点同步服务器时间
0 7 * * * ntpdate 192.168.1.112
(2) 每两个小时执行一次
0 */2 * * * echo "2 minutes later" >> /tmp/output.txt
(3) 每周五早上十点写周报
0 10 * * * 5 /home/jerryzhang/update_weekly.py
(4) 每天 6, 12, 18 点执行一次命令
0 6,12,18 * * * /bin/echo hello
(5) 每天 13, 14, 15, 16, 17 点执行一次命令
0 13-17 * * * /bin/echo hello
__注:__
* 程序执行完毕,系统会给对应用户发送邮件,显示该程序执行内容,如果不想收到,可以重定向内容 `> /dev/null 2>&1`
* 如果执行语句中有 `%` 号,需要使用反斜杠 '\' 转义
# rsync
## rsync 基本介绍
`rsync` 是类 unix 系统下的数据镜像备份工具,从软件的命名上就可以看出来了—— remote sync。它的特性如下:
* 1、可以镜像保存整个目录树和文件系统
* 2、可以很容易做到保持原来文件的权限、时间、软硬链接等等
* 3、无须特殊权限即可安装
* 4、优化的流程,文件传输效率高
* 5、可以使用 rsh、ssh 等方式来传输文件,当然也可以通过直接的 socket 连接
* 6、支持匿名传输
在使用 rsync 进行远程同步时,可以使用两种方式:__远程 Shell 方式__(用户验证由 ssh 负责)和 __C/S 方式__(即客户连接远程 rsync 服务器,用户验证由 rsync 服务器负责)。
无论本地同步目录还是远程同步数据,首次运行时将会把全部文件拷贝一次,以后再运行时将只拷贝有变化的文件(对于新文件)或文件的变化部分(对于原有文件)。
## rsync 工作场景
> * 两台服务器之间数据同步。
> * 把所有客户服务器数据同步到备份服务器,生产场景集群架构服务器备份方案。
> * rsync 结合 inotify 的功能做实时的数据同步。
## 使用方法
rsync 可以使用 ssh 和 C/S 方式进行传输文件,以下使用 ssh 方式
```
rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST # 执行“推”操作
or rsync [OPTION]... [USER@]HOST:SRC [DEST] # 执行“拉”操作
```
### rsync 选项
```
Usage: rsync [OPTION]... SRC [SRC]... DEST
or rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
or rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
or rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST
or rsync [OPTION]... [USER@]HOST:SRC [DEST]
or rsync [OPTION]... [USER@]HOST::SRC [DEST]
or rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]
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.
```
__注:__ 在指定复制源时,路径是否有最后的 “/” 有不同的含义,例如:
* /data :表示将整个 /data 目录复制到目标目录
* /data/ :表示将 /data/ 目录中的所有内容复制到目标目录
### 常用选项
* `-v` : Verbose (try -vv for more detailed information) # 详细模式显示
* `-e` "ssh options" : specify the ssh as remote shell # 指定 ssh 作为远程 shell
* `-a` : archive mode # 归档模式,表示以递归方式传输文件,并保持所有文件属性,等于 -rlptgoD
* `-r`(--recursive) : 目录递归
* `-l`(--links) :保留软链接
* `-p`(--perms) :保留文件权限
* `-t`(--times) :保留文件时间信息
* `-g`(--group) :保留属组信息
* `-o`(--owner) :保留文件属主信息
* `-D`(--devices) :保留设备文件信息
* `-z` : 压缩文件
* `-h` : 以可读方式输出
* `-H` : 复制硬链接
* `-X` : 保留扩展属性
* `-A` : 保留 ACL 属性
* `-n` : 只测试输出而不正真执行命令,推荐使用,特别防止 `--delete` 误删除!
* `--stats` : 输出文件传输的状态
* `--progress` : 输出文件传输的进度
* `––exclude=PATTERN` : 指定排除一个不需要传输的文件匹配模式
* `––exclude-from=FILE` : 从 FILE 中读取排除规则
* `––include=PATTERN` : 指定需要传输的文件匹配模式
* `––include-from=FILE` : 从 FILE 中读取包含规则
* `--numeric-ids` : 不映射 uid/gid 到 user/group 的名字
* `-S, --sparse` : 对稀疏文件进行特殊处理以节省 DST 的空间(有空洞文件时使用)
* `--delete` : 删除 DST 中 SRC 没有的文件,也就是所谓的镜像 [mirror] 备份
* `-P` 等同于 `--partial` 保留那些因故没有完全传输的文件,以是加快随后的再次传输
## 一些命令
### 常用命令
```
#rsync -avzP --delete [SRC] [DEST]
```
__注:__ 日常传输时参数记不清楚时,只需要加 `-a` 参数即可,如果有稀疏文件,则添加 `-S` 选项可以提升传输性能。
```
[tips]
稀疏文件(Sparse File)
在 UNIX 文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞。位于文件中但没有写过的字节都被设为 0。
稀疏文件与其他普通文件基本相同,区别在于文件中的部分数据是全 0,且这部分数据不占用磁盘空间。
下面是稀疏文件的创建与查看方法
[root@Linux ceshi]# dd if=/dev/zero of=sparse-file bs=1 count=1 seek=1024k
[root@Linux ceshi]# ls -l sparse-file
-rw-r--r-- 1 root root 1048577 6 月 19 10:20 sparse-file
[root@Linux ceshi]# du -sh sparse-file
4.0K sparse-file
[root@Linux ceshi]# cat sparse-file >> meetbill_file
[root@Linux ceshi]# du -sh meetbill_file
1.1M meetbill_file
[root@Linux ceshi]# ll
总用量 1032
-rw-r--r-- 1 root root 1048577 6 月 19 10:21 meetbill_file
-rw-r--r-- 1 root root 1048577 6 月 19 10:20 sparse-file
[root@Linux ceshi]# ll -h
总用量 1.1M
-rw-r--r-- 1 root root 1.1M 6 月 19 10:21 meetbill_file
-rw-r--r-- 1 root root 1.1M 6 月 19 10:20 sparse-file
```
### ssh 端口非默认 22 同步
使用 ssh 方式传输时如果连接服务器 ssh 端口非标准,则需要通过 `-e` 选项指定:
```
#rsync -avzP --delete -e "ssh -p 22222" [USER@]HOST:SRC [DEST]
```
### ssh 自动接受公钥和修改 known_hosts 文件
```
#rsync -a -e "ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no" [USER@]HOST:SRC [DEST]
```
## inotify+rsync 实现实时文件同步
### 存储数据异地灾备
#### 需求背景
服务器文件需要实时同步,即使是轮询,也存在同步延迟,inotify 的出现让真正的实时成为了现实
我们可以用 inotify 去监控文件系统的事件变化,一旦有我们期望的事件发生,就使用 rsync 进行冗余同步
#### 架构
| 用途 | IP |
| ------------- |:-------------:|
| 服务端 A| 192.168.199.101 |
| 服务器 B(备份服务器) | 192.168.199.102|
```
+--------+ +-------------------+
|服务器 A |--------->|服务器 B(备份服务器)|
+--------+ +-------------------+
inotify+rsync rsync
```
#### 脚本内容
所有配置只需要在服务器 A 上配置即可
(1) 安装 `inotify-tools`(yum -y install inotify-tools)
(2) 配置服务器 A 使用秘钥登录服务器 B
(3) 在服务器 A 上编写脚本,主要配置服务器 B 的机器 IP,登录用户,以及服务器器 A 的存储目录和存储数据异地灾备目录
将此文件保存到 /opt/inotify_rsync.sh
``` bash
#!/bin/bash
host=192.168.199.102
user=root
# 服务器存储目录
src='/tmp/src1/'
# 存储数据异地灾备目录
dest='/tmp/dest1'
inotifywait -mrq -e modify,attrib,moved_to,moved_from,move,move_self,create,delete,delete_self --timefmt='%d/%m/%y %H:%M' --format='%T %w%f %e' $src | while read chgeFile
do
rsync -avPz --delete $src $user@$host:$dest &>>./rsync.log
done
```
下载脚本
```
#curl -o inotify_rsync.sh https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/service/inotify_rsync.sh
```
(3) 启动异地灾备程序
```
#nohup /bin/bash /opt/inotify_rsync.sh & // 后台不挂断地运行命令
#echo "nohup /bin/bash /opt/inotify_rsync.sh &" >> /etc/rc.local // 设置 linux 服务器启动自动启动 nohup
```
#### 原理
1. 使用 inotifywait 监控文件系统时间变化
2. while 通过管道符接受内容,传给 read 命令
3. read 读取到内容,则执行 rsync 程序
### 常见问题
#### 对大磁盘进行 inotify 监听时出错
```
Failed to watch /mnt/;upper limit on inotify watches reached!
Please increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches’.`
```
cat 一下这个文件,默认值是 8192,echo 8192000 > /proc/sys/fs/inotify/max_user_watches 即可~
# telnet-server
## 安装使用
```
#curl -o telnet-server.tar.gz https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/service/telnet-server.tar.gz
#tar -zxvf telnet-server.tar.gz
#cd telnet-server*
#sh start.sh
```
执行程序后有三项,执行第一项可以进行安装并启动 telnet-server,第二项会关闭 telnet-server 并将开机自动启动关闭
## 测试
需要测试 telnet 是否成功开启
```
#telnet localhost
```
输入用户名密码能登录成功。同时需要测试下其他机器远程 telnet 是否成功,如果不成功,那么很有可能是防火墙的问题
```
#iptables -I INPUT -p tcp --dport 23 -jACCEPT
#service iptables save
#service iptables restart
```
# ftp
## ftp 简介
ftp 工作会启动两个通道:控制通道 , 数据通道。在 ftp 协议中,控制连接均是由客户端发起的,而数据连接有两种模式:port 模式(主动模式)和 pasv 模式(被动模式)
* **PORT 模式:**
在客户端需要接收数据时,ftp_client (大于 1024 的随机端口) —> PORT 命令 —> ftp_server (21) 发送 PORT 命令,这个 PORT 命令包含了客户端是用什么端口来接收数据(大于 1024 的随机端口),在传送数据时, ftp_server 将通过自己的 TCP 20 端口和 PORT 中包含的端口建立新的连接来传送数据。
* **PASV 模式:**
传送数据时,ftp_client —> PASV 命令 —> ftp_server(21) 发送 PASV 命令时,ftp_server 自动打开一个 1024--5000 之间的随机端口并且通知 ftp_client 在这个端口上传送数据,然后客户端向指定的端口发出请求连接,建立一条数据链路进行数据传输。
如果想对访问 FTP 的帐户给予更多的权限,可以用本地帐户来实现。但是,本地帐户默认情况下是可以登陆 Linux 系统的,这样对 Linux 系统来说是一个安全隐患。那么怎么能在灵活的赋予 FTP 用户权限的前提下,保证 FTP 服务器乃至整个 Linux 系统的安全呢?使用虚拟用户就是一种解决办法
安装包
> * vsftpd
> * db4*
## 安装配置
```
[root@meetbill ~]#curl -o ftptool.sh https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/service/ftptool.sh
[root@meetbill ~]#chmod +x ftptool.sh
[root@meetbill ~]#./ftptool.sh install_server
[root@meetbill ~]#./ftptool.sh add_user
[root@meetbill ~]#./ftptool.sh start
```
## 设置 FTP 虚拟账号密码
### 修改 proftpd 配置文件(早期系统镜像的默认 FTP 配置路径为 /etc/proftpd.conf)
```
注释<Anonymous ~ftp> … </Anonymous>之间相关配置
<Anonymous ~ftp> … </Anonymous>之外新增如下配置
AuthOrder mod_auth_file.c
AuthUserFile /etc/proftpd.passwd
RequireValidShell off
```
### 获取 ftpasswd 用于设置虚拟账号
拉取 proftpd 源码包中的 ftpasswd 文件
```
cd /usr/sbin;wget https://github.com/downloads/proftpd/proftpd.github.com/proftpd-1.3.4b.tar.gz -O proftpd-1.3.4b.tar.gz; tar -zxvf proftpd-1.3.4b.tar.gz; mv ./proftpd-1.3.4b/contrib/ftpasswd .;rm -rf proftpd-1.3.4b*
```
### 设置账号密码
```
ftpasswd --file=/etc/proftpd.passwd --home=xxx --shell=/bin/false --name=xxx --uid=99 --gid=99 --passwd
```
配置说明
> * --home=xxx 指定 ftp 用户登录后的根目录(eg. --home=/home)
> * --name=xxx 指定 ftp 用户名
> * --uid=99 --gid=99 指定账号关联对应系统用户和组
> * wget 获取文件路径以–home 指定路径为基础进行拼接(eg. --home=/home; wget ftp://…/home/work => wget ftp://…/work )
> demo
```
id meetbill
uid=500(meetbill) gid=501(meetbill) groups=501(meetbill)
ftpasswd --file=/etc/proftpd.passwd --home=/home/meetbill/ --shell=/bin/false --name=meetbill --uid=500 --gid=501 --passwd
这里会输入两次密码
```
### 删除 ftpasswd
```
cd /usr/sbin; rm -f ftpasswd
```
### 重启 FTP 服务(如启动失败,proftpd -t -d5 检查配置文件出错点)
```
service proftpd restart
```
### wget 使用
```
wget --ftp-user=meetbill --ftp-password=xxxxxxxxx ftp://xxxx/test_dif/test_file -O test_file
test_dif/test_file 在物理机上的绝对路径为 /home/meetbill/test_dif/test_file
```
================================================
FILE: doc/Linux/shell.md
================================================
# Shell 基础及实例
<!-- vim-markdown-toc GFM -->
* [1 shell 编程环境](#1-shell-编程环境)
* [1.1 编程基础知识](#11-编程基础知识)
* [1.1.1 程序编程风格](#111-程序编程风格)
* [1.1.2 程序的执行方式](#112-程序的执行方式)
* [1.1.3 shell 脚本](#113-shell-脚本)
* [1.1.4 运行脚本的两种方式](#114-运行脚本的两种方式)
* [1.2 Bash 编程](#12-bash-编程)
* [1.3 逻辑运算](#13-逻辑运算)
* [2 bash 变量类型](#2-bash-变量类型)
* [2.1 强弱类型语言的区别](#21-强弱类型语言的区别)
* [2.2 Bash 中的变量](#22-bash-中的变量)
* [2.2.1 本地变量](#221-本地变量)
* [2.2.2 环境变量](#222-环境变量)
* [2.2.3 只读变量](#223-只读变量)
* [2.2.4 位置变量](#224-位置变量)
* [2.3 变量特殊用法](#23-变量特殊用法)
* [2.3.1 将多行结果赋值给变量](#231-将多行结果赋值给变量)
* [2.3.2 去除行结果后的特殊字符](#232-去除行结果后的特殊字符)
* [3 bash 的配置文件](#3-bash-的配置文件)
* [4 bash 中的算术运算符](#4-bash-中的算术运算符)
* [5 条件测试](#5-条件测试)
* [Bash 的测试类型](#bash-的测试类型)
* [文件测试](#文件测试)
* [6 bash 脚本编程之用户交互](#6-bash-脚本编程之用户交互)
* [7 流程控制](#7-流程控制)
* [if 语句](#if-语句)
* [for 循环](#for-循环)
* [for 循环基础](#for-循环基础)
* [for 循环的特殊格式](#for-循环的特殊格式)
* [while 循环](#while-循环)
* [while 基础](#while-基础)
* [创建死循环](#创建死循环)
* [while 循环遍历文件的每一行](#while-循环遍历文件的每一行)
* [while 与 for 的区别](#while-与-for-的区别)
* [行读取](#行读取)
* [ssh 命令操作](#ssh-命令操作)
* [case 语句](#case-语句)
* [8 函数](#8-函数)
* [函数基础](#函数基础)
* [Example 编写一个服务启动关闭脚本](#example-编写一个服务启动关闭脚本)
* [函数返回值](#函数返回值)
* [Example 求 N 的阶乘](#example-求-n-的阶乘)
* [9 数组](#9-数组)
* [数组](#数组)
* [定义](#定义)
* [引用数组中的元素](#引用数组中的元素)
* [10 bash 的字符串处理工具](#10-bash-的字符串处理工具)
* [字符串切片](#字符串切片)
* [基于模式取子串](#基于模式取子串)
* [查找替换](#查找替换)
* [查找并删除](#查找并删除)
* [字符大小写转换](#字符大小写转换)
* [变量赋值](#变量赋值)
* [11 Bash 命令自动补全](#11-bash-命令自动补全)
* [11.1 内置补全命令](#111-内置补全命令)
* [11.2 编写脚本](#112-编写脚本)
* [11.2.1 支持主选项](#1121-支持主选项)
* [11.2.2 支持子选项](#1122-支持子选项)
* [11.2.3 安装补全脚本](#1123-安装补全脚本)
* [12 常用实例](#12-常用实例)
* [12.1 推荐添加内容](#121-推荐添加内容)
* [12.2 脚本的配置文件](#122-脚本的配置文件)
* [12.3 ssh 登录相关](#123-ssh-登录相关)
* [12.4 ping 文件列表中所有主机](#124-ping-文件列表中所有主机)
* [12.5 shell 模板变量替换](#125-shell-模板变量替换)
* [应用场景](#应用场景)
* [使用方式](#使用方式)
* [13 日常使用库](#13-日常使用库)
<!-- vim-markdown-toc -->
## 1 shell 编程环境
### 1.1 编程基础知识
#### 1.1.1 程序编程风格
> * 过程式:以指令为中心,数据服务于指令;
> * 对象式:以数据为中心,指令服务于数据;
shell 程序,提供了编程能力,解释执行,shell 就是一解释器;
#### 1.1.2 程序的执行方式
过程式编程的三种结构;
> * 顺序执行
> * 循环执行
> * 选择执行
#### 1.1.3 shell 脚本
首行特定格式:
`#!/bin/bash`
#### 1.1.4 运行脚本的两种方式
> * a、 给予执行权限,通过具体的文件路径指定文件执行;
> * b、 直接运行解释器,将脚本作为解释器程序的参数运行;
Example
```
[root@localhost test1]# vim test.sh
[root@localhost test1]# bash test.sh
hello,girl
[root@localhost test1]# chmod +x test.sh
[root@localhost test1]# ./test.sh
hello,girl
```
### 1.2 Bash 编程
> * bash 是弱类型编程,变量默认为字符型;
> * 把所有要存储的数据统统当做字符进行存储;
> * 变量不需要事先声明,可以在调用时直接赋值使用,参与运算会自动进行隐式类型转换;
> * 不支持浮点数;
### 1.3 逻辑运算
> * 与:&& 同为 1 则为 1,否则为 0;
> * 或:|| 同为 0 则为 0,否则为 1;
> * 非:取反,!0 为 1,!1 为 0;
> * 短路与运算:双目运算符前面的结果为 0,则结果一定为 0,后面的不执行;
> * 短路或运算:双目运算符前面的结果为 1,则结果一定为 1,后面的不执行;
## 2 bash 变量类型
变量类型决定了变量的数据存储格式、存储空间大小以及变量能参与的运算种类;
### 2.1 强弱类型语言的区别
> * 强类型:定义变量时必须执行类型、参与运算必须符合类型要求;调用未声明变量会产生错误;
> * 弱类型:无需指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无需事先定义即可直接调用;
### 2.2 Bash 中的变量
根据变量的生效范围等标准划分:
> * 本地变量:生效范围为当前 shell 进程;对当前 shell 之外的其他 shell 进程,包括当前 shell 的子 shell 进程均无效;
> * 环境变量:生效范围为当前 shell 进程及其子进程;
> * 局部变量:生效范围为当前 shell 进程中某代码片断(通常指函数)
> * 位置变量:`$1, $2, ...` 来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数;
> * 特殊变量:`$?, $0, $*, $@, $#`
> * `$$`: 代表所在命令的 PID(不常用)
> * `$!`: 代表最后执行的后台命令的 PID(不常用)
> * `$?`: 上一条命令的执行状态结果
> * `$0`: 命令本身
> * `$*`: 传递给脚本的所有参数,以一对双引号给出参数列表
> * `$@`: 传递给脚本的所有参数,将各个参数分别加双引号返回
> * `$#` : 传递给脚本的参数的个数;
#### 2.2.1 本地变量
变量赋值:name='VALUE'
```
a) 在赋值时,VALUE 可以使用以下引用:
【1】可以是直接字符串;name="username"
【2】变量引用:name=“$username”
【3】命令引用:name=`COMMAND`,name=$(COMMAND)
```
变量引用:`${name}`, 花括号可省略:`$name`
引号引用:
> * `" "`: 弱引用,其中的变量引用会被替换为变量值;
> * `' '`: 强引用,其中的变量不会被替换为变量值,而保持原字符串;
查看所有已定义的变量: #set
销毁变量: # unset name
#### 2.2.2 环境变量
```
变量声明、赋值:
export name=VALUE
declare -x name=VALUE
变量引用:
$name
${name}
显示所有环境变量:
export
env
printenv
销毁环境变量:
unset name
```
Bash 中内建的环境变量:
> * PATH,SHELL,UID,HISTSIZE, HOME, PWD, OLD, HISTFILE, PS1
#### 2.2.3 只读变量
相当于常量,变量值不可变,不能再进行赋值运算;
声明只读变量的格式:
```
readonly name
declare –r name
```
#### 2.2.4 位置变量
在脚本代码中调用通过命令行传递给脚本的参数;
```
$1,$2,…. : 对应调用第 1、第 2 等参数;
$0: 命令本身;
$*: 传递给脚本的所有参数;
$@: 传递给脚本的所有参数;
$#: 传递给脚本的参数的个数;
```
### 2.3 变量特殊用法
#### 2.3.1 将多行结果赋值给变量
将多行结果赋值给变量时使用变量时需要注意
如:ls_data=$(ls -l)
> * 在 Mac 上直接输出 echo ${ls_data} 即正常结果
> * 在 CentOS 上操作时
> * echo ${ls_data} 时为不换行内容,整体只有一行
> * echo "${ls_data}" 时内容输出正常
比如要获取 redis 的 info 信息时,不加双引号输出时,输出的内容仅仅为多行结果的最后一行,加双引号输出时可以正常输出
#### 2.3.2 去除行结果后的特殊字符
如要去掉 `^M`
`echo ${variable}|tr -d '\r'`
## 3 bash 的配置文件
> * 全局配置:
> * /etc/profile
> * /etc/profile.d/*.sh
> * /etc/bashrc
> * 用户配置:
> * ~/.bash_profile
> * ~/.bashrc
## 4 bash 中的算术运算符
`+,-,*,/,%,**`
实现算术运算的方式:
(1) let var= 算术表达式
(2) var=$『算术表达式』
(3) var=$((算术表达式))
(4) var=$(expr arg1 arg2 arg3...)
乘法符号在有些场景中需要转义;
- bash 内建随机数生成器:$RANDOM
- 增强型赋值:
+=,-=,*=,/=,%=
let varOPERvalue:
例如:letcount+=1
- 自增,自减:
let var+=1
let var++
let var-=1
let var--
## 5 条件测试
判断某需求是否满足,需要有测试机制来实现;
- Note:专用的测试表达式需要由测试命令辅助完成测试过程;
- 测试命令:
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
Note: EXPRESSION 前后必须有空白字符,否则报错;
### Bash 的测试类型
- 数值测试:
-gt : 是否大于; >
-ge:是否大于等于; >=
-eq:是否等于 ==
-ne:是否不能于 !=
-lt :是否小于 <
-le :是否小于等于; <=
- 字符串测试:
> * `==` : 是否等于;
> * `>` : 是否大于;
> * `<` : 是否小于;
> * `!=` : 是否不等于;
> * `=~` : 左侧字符串是否能够被右侧的 PATTERN 所匹配;
- Note:此表达式一般用于 [[ ]] 中;
-z “STRING” : 测试字符串是否为空,空则为真,不空则为假;
-n “STRING” :测试字符串是否不空,不空则为真,空则为假;
Note:在字符串比较时用到的操作数都应该使用引号;
### 文件测试
- (a) 存在性测试:
-a FILE
-e FILE:文件存在性测试,存在为真,否则为假;
- (b) 存在性及类别测试
-b FILE :是否存在且为块设备文件;
-c FILE :是否存在且为字符设备文件;
-d FILE :是否存在且为目录文件;
-f FILE :是否存在且为普通文件;
-h FILE 或 –L FILE:是否存在且为符号链接文件;
-p FILE:是否存在且为命名管道文件;
-S FILE :是否存在且为套接字文件;
- (c) 文件权限测试:
-r FILE:是否存在且可读
-w FILE: 是否存在且可写
-x FILE: 是否存在且可执行
- (d) 文件特殊权限测试:
-g FILE:是否存在且拥有 sgid 权限;
-u FILE:是否存在且拥有 suid 权限;
-k FILE:是否存在且拥有 sticky 权限;
- (e) 文件大小测试:
-s FILE:是否存在且非空;
- (f) 文件是否打开:
-t fd:fd 表示文件描述符是否已经打开且与某终端相关
-N FILE:文件自从上一次被读取之后是否被修改过;
-O FILE:当前有效用户是否为文件属主;
-G FILE:当前有效用户是否为文件属组;
- (g) 双目测试:
FILE1 –ef FILE2 : FILE1 与 FILE2 是否指向同一个设备上的相同 inode;
FILE1 –nt FILE2 :FILE 是否新于 FILE2;
FILE1 –ot FILE2 :FILE1 是否旧于 FILE2;
- (h) 组合测试条件:
完成逻辑运算:
第一种方式:
COMMAND1 && COMMAND2
COMMAND1 || COMMAND2
!COMMAND
eg:[ -e FILE ] && [ -r FILE ] 文件是否存在且是否有读权限;
第二种方式:
EXPRESSION1 –a EXPRESSION2
EXPRESSION1 –o EXPRESSION2
!EXPRESSION
必须使用测试命令进行;
- Example
```
# [ -z "$hostName" -o "$hostName"=="localhost.localdomain" ] && hostname hostname_w
# -z 判断 hostName 是否为空,-o 表示或者,即:hostName 为空或者值为 localhot.localdomain 的时候,使用 hostname 命令修改主机名;
```
- Example
```
[root@bill ~]# [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
#判断文件 /bin/cat 是否存在且是否有可执行权限,&& 是短路与,如果前面执行结果为真则使用 cat 命令#查看文件;
#
# /etc/fstab
# Created by anaconda on Fri Jul 3 03:08:29 2015
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/centos-root / xfs defaults 0 0
UUID=3d04d82b-c52b-4184-8d64-1826db6e2eac /boot xfs defaults 0 0
/dev/mapper/centos-home /home xfs defaults 0 0
/dev/mapper/centos-swap swap swap defaults 0 0
```
- 快速查找选项定义的方法:
man bash --> /^[[:space:]]*-f
```
# [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab
#如果文件存在且有执行权限,就用它查看文件内容;
```
## 6 bash 脚本编程之用户交互
- read 命令:
read [option]… [name….]
-p “prompt” 提示符;
-t TIMEOUT 用户输入超时时间;
- 检测脚本中的语法错误:
bash –n /path/to/some_script
- 调试执行,查看执行流程:
bash –x /path/to/some_script
- Example
```
#!/bin/bash
#
#Description: Test read command's grammer.
read -t 50 -p "Enter a disk special file:" diskfile #将用户输入的内容赋值给 diskfile 变量
[ -z "$diskfile" ] && echo "Fool" && exit 1
#判断 diskfile 的值是否为空,如果为空则输出,并退出;
if fdisk -l | grep "^Disk $diskfile" &> /dev/null;then
fdisk -l $diskfile
else
echo "Wrong disk special file."
exit 2
fi
```
## 7 流程控制
### if 语句
```
if 语句:
CONDITION:
bash 命令:
```
用命令的执行状态结果:
成功:true,即执行状态结果值为 0 时;
失败:false,即执行状态结果为 1-255 时;
成功或失败的定义:取决于用到的命令;
- 单分支 if:
```
if CONDITION;then
if-true(条件为真时的执行语句集合)
fi
```
- Example
```
#!/bin/bash
#if 单分支语句测试;
if [ $UID -eq 0 ];then
echo "It's amdinistrator."
fi
```
- 双分支 if:
```
if CONDITION;then
if-true
else
if-false
fi
```
- Example
```
#!/bin/bash
#
#if 双分支语句测试;
if [ $UID -eq 0 ];then
echo "It's administrator."
else
echo "It's Comman User."
fi
```
- 多分支 if:
```
if CONDITION1;then
if-true
elif CONDITION2;then
if-true
elif CONDITION3;then
if-true
….
else
all-false
fi
```
逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束;
- Exmaple: 用户键入文件路径,脚本来判断文件类型;
```
#!/bin/bash
#
#if 语句多分支语句;
read -t 20 -p "Enter a file path:" filename
if [ -z "$filename" ];then #判断变量是否为空;
echo "Usage:Enter a file path."
exit 2
fi
if [ ! -e $filename ];then #判断用户输入的文件是否存在;
echo "No such file."
exit 3
fi
if [ -f $filename ];then #判断是否为普通文件;
echo "A common file."
elif [ -d $filename ];then #判断是否为目录;
echo "A directory"
elif [ -L $filename ];then #判断是否为链接文件;
echo "A symbolic file."
else
echo "Other type."
fi
```
### for 循环
#### for 循环基础
循环体:要执行的代码,可能要执行 n 遍;
循环需具备进入循环的条件和退出循环的条件;
- for 循环:
```
for 变量名 in 列表;do
循环体
done
```
- 执行机制:
依次将列表中的元素赋值给“变量”;每次赋值后即执行一次循环体;直到列表中的元素耗尽,循环结束;
** Example 添加 10 个用户,用户名为:user1-user10:密码同用户名**
```
#!/bin/bash
#
#for 循环,使用列表;
#添加 10 个用户,user1-user10
if [ ! $UID -eq 0 ];then #判断执行脚本的是否为 root 用户,若不是则直接退出;
echo "Only root can use this script."
exit 1
fi
for i in {1..10};do
if id user$i &> /dev/null;then #判断用户是否已经存在;
echo "user$i exists"
else
useradd user$i
if [ $? -eq 0 ];then #判断前一条命令是否执行成功;
echo "user$i" | passwd --stdin user$i &> /dev/null
#passwd 命令从标准输入获得命令,即管道前命令的执行结果;
fi
fi
done
```
- 列表生成方式:
(1) 直接给出列表 for i in {bill johnson rechard}
(2) 整数列表
(a) {start..end}
(b) $(seq [start [step]] end)
(3) 返回列表的命令
$(COMMAND) --> 如:$(ls /var)
(4) glob
/etc/rc.d/rc3.d/K*
(5) 变量引用
$@ , $* -->所有向脚本传递的参数;
**Example 判断某路径下所有文件的类型**
```
#!/bin/bash
#
#for 循环使用命令返回列表;
for file in $(ls /var);do #使用命令生成列表;
if [ -f /var/$file ];then
echo "Common file."
elif [ -L /var/$file ];then
echo "Symbolic file."
elif [ -d /var/$file ];then
echo "Directory."
else
echo "Other type"
fi
done
```
**Example 使用 for 循环统计关于 tcp 端口监听状态**
```
#!/bin/bash
#
#使用 for 循环过滤 netstat 命令中关于 tcp 的信息;
declare -i estab=0
declare -i listen=0
declare -i other=0
for state in $( netstat -tan | grep "^tcp\>" | awk '{print $NF}');do
if [ "$state" == 'ESTABLISHED' ];then
let estab++
elif [ "$state" == 'LISTEN' ];then
let listen++
else
let other++
fi
done
echo "ESTABLISHED:$estab"
echo "LISTEN:$listen"
echo "Unknow:$other"
```
#### for 循环的特殊格式
```
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式));do
循环体
done
```
此种格式和 C 语言等的格式是一样一样的,只是多了一对括号;
控制变量初始化:仅在运行到循环代码段时执行一次;
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再在条件判断;
**Example 求 100 以内所有正整数之和**
```
#!/bin/bash
#
#for 循环,类似 C 语言格式,求 100 以内正整数之和;
declare -i sum=0
for ((i=1;i<=100;i++));do
let sum+=$i
done
echo "Sum:$sum."
```
### while 循环
#### while 基础
语法:
```
while CONDITION;do
循环体
done
```
```
CONDITION:循环控制条件;进入循环之前,先做一次判断;
每一次循环之后会再次做判断;条件为“true”,则执行一次循环;
直到条件测试状态为“false”终止循环;
因此:CONDTION 一般应该有循环控制变量;
而此变量的值会在循环体不断地被修正,直到最终条件为 false,结束循环。
```
**Example 用 while 求 100 以内所有正整数之和**
```
#!/bin/bash
#使用 while 求 100 以内正整数之和;
declare -i sum=0
declare -i i=1
while [ $i -le 100 ];do
let sum+=$i
let i++
done
echo $i
echo "Summary:$sum."
```
**Example 用 while 添加 10 个用户**
```
#!/bin/bash
#
#使用 while 循环添加 10 个用户
declare -i i=1
declare -i users=0
while [ $i -le 10 ];do
if ! id user$i &> /dev/null;then
useradd user$i
echo "Add user: user$i"
let users++
fi
let i++
done
echo "Add $users users."
```
**Example 利用 RANDOM 生成 10 个随机数字,输出这 10 个数字,并显示其中的最大者和最小者**
```
#!/bin/bash
#
#利用 RANDOM 生成 10 个随机数,输出,并求最大值和最小值;
declare -i max=0
declare -i min=0
declare -i i=1
while [ $i -le 9 ];do
rand=$RANDOM
echo $rand
if [ $i -eq 1 ];then
max=$rand
min=$rand
fi
if [ $rand -gt $max ];then
max=$rand
fi
if [ $rand -lt $min ];then
min=$rand
fi
let i++
done
echo "MAX:$max."
echo "MIN:$min."
```
#### 创建死循环
```
while true;do
循环体
done
```
```
until false;do
循环体
done
```
**Example 每隔 3 秒钟到系统上获取已经登录的用户信息;如果用户输入的用户名登录了,则记录于日志中,并退出**
```
#!/bin/bash
#
#用 while 造成死循环,在系统上每隔 3 秒判断一次用户输入的用户名是否登录;
read -p "Enter a user name:" username
while true;do
if who | grep "^$username" &> /dev/null;then
break
fi
sleep 3
done
echo "$username logggen on." >> /tmp/user.log
```
#### while 循环遍历文件的每一行
```
while read line;do
循环体
done < /PATH/FROM/SOMEFILE
```
依次读取 /PATH/FROM/SOMEFILE 文件中的每一行,且将该行赋值给变量 line;
另一种也很常见的用法【推荐】:
```
command | while read line
do
...
done
```
**Example 依次读取 /etc/passwd 文件中的每一行,找出其 ID 号为偶数的所有用户,显示其用户名、ID 号及默认 shell**
```
#!/bin/bash
#while 循环的特殊用法 读取指定文件的每一行并赋值给变量
while read line;do
if [ $[`echo $line | cut -d: -f3` % 2] -eq 0 ];then
echo -e -n "username:`echo $line | cut -d: -f1`\t"
echo -e -n "uid: `echo $line | cut -d: -f3`\t"
echo "SHELL:`echo $line | cut -d: -f7`"
fi
done < /etc/passwd
```
#### while 与 for 的区别
##### 行读取
> * while 循环
> * 以行读取文件
> * for 循环
> * 以空格和回车符分割读取文件,也就是碰到空格和回车,都会执行循环体,所以需要以行读取的话,就要把文件行中的空格转换成其他字符。
##### ssh 命令操作
> * for 循环
> * for line in $(cat $file) 在循环体中进行 ssh 命令操作可以依次执行
> * while 循环
> * 循环体内有 ssh、scp、sshpass 的时候会执行一次循环就退出的情况,解决该问题方法有如下两种
> * a、使用`ssh -n "command"` ;
> * b、将 while 循环内加入 null 重定向,如 `ssh "cmd" < /dev/null` 将 ssh 的输入重定向输入。
### case 语句
```
case 变量引用 in
PAT1)
分支 1
;
PAT2)
分支 2
;
….
*)
默认分支
;
esac
```
case 支持 glob 风格的通配符:
*: 任意长度任意字符;
?: 任意单个字符;
[]:指定范围内的任意单个字符;
a|b: a 或 b
** Example 使用 case 语句改写前一个练习 **
```
#!/bin/bash
#
cat << EOF
cpu) show cpu information;
mem) show memory information;
disk) show disk information;
quit) quit
============================
EOF
read -p "Enter a option: " option
while [ "$option" != 'cpu' -a "$option" != 'mem' -a "$option" != 'disk' -a "$option" != 'quit' ]; do
read -p "Wrong option, Enter again: " option
done
case "$option" in
cpu)
lscpu
;;
mem)
cat /proc/meminfo
;;
disk)
fdisk -l
;;
*)
echo "Quit..."
exit 0
;;
esac
```
## 8 函数
### 函数基础
函数的作用:
过程式编程:为实现代码重用
模块化编程
结构化编程;
- 语法一:
```
function f_name {
…函数体…..
}
```
- 语法二:
```
f_name() {
…函数….
}
```
- 函数调用:函数只有被调用才会执行:
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码;
- 函数的生命周期:被调用时创建,返回时终止;
return 命令返回自定义状态结果;
0:成功
1-255:失败
**Example 通过函数,创建 10 个用户**
```
#!/bin/bash
#
#通过调用函数添加 10 个用户
function adduser {
if id $username &> /dev/null;then
echo "$username exists."
return 1
else
useradd $username
[ $? -eq 0 ] && echo "Add $username finished." && return 0
fi
}
for i in {1..10};do
username=myuser$i
adduser
done
```
#### Example 编写一个服务启动关闭脚本
```
#!/bin/bash
# chkconfig: - 88 12
# description: test service script
prog=$(basename $0)
lockfile=/var/lock/subsys/$prog
start() {
if [ -e $lockfile ];then
echo "$prog is already running."
return 0
else
touch $lockfile
[ $? -eq 0 ] && echo "Starting $prog finished."
fi
}
stop() {
if [ -e $lockfile ];then
rm -f $lockfile && echo "Stop $prog ok."
else
echo "$prog is stopped yet."
fi
}
status() {
if [ -e $lockfile ];then
echo "$prog is running."
else
echo "$prog is stopped."
fi
}
usage() {
echo "Usage:$prog {start | stop | restart | status}"
}
if [ $# -lt 1 ];then
usage
exit 1
fi
case $1 in
start)
start
;;
stop)
stop
;;
restart)
stop
start
;;
status)
status
;;
*)
usage
esac
```
### 函数返回值
函数的执行结果返回值:
(1) 使用 echo 或 print 命令进行输出;
(2) 函数体中调用命令的执行结果;
函数的退出状态码:
(1) 默认取决于函数体中执行的最后一条命令的退出状态码;
(2) 自定义退出状态码;
使用 return 关键字;
函数可以接受参数:
传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;
例如:“testfunc arg1 arg2 …”
在函数体当中,可使用 $1,$2,…. 调用这些参数;还可以使用 $@,$*,$#等特殊变量;
```
#!/bin/bash
#
#使用带参数的函数添加 10 个用户
function adduser { #一个可接受参数的函数
if [ $# -lt 1 ];then
return 2 # 2: no arguments
fi
if id $1 &> /dev/null;then
echo "$1 exists."
return 1;
else
useradd $1
[ $? -eq 0 ] && echo "Add $1 finished." && return 0
fi
}
while true;do #死循环,直到输入的值为 quit 是退出;
read -t 20 -p "Please input your username(quit to cancel):" username
if [ $username == "quit" ];then
echo "Quit."
exit 0
else
adduser $username
fi
done
```
- 变量作用域:
本地变量:当前 shell 进程,为了执行脚本会启动专用的 shell 进程;因此,本地变量的作用范围是当前 shell 脚本程序文件;
局部变量:函数的生命周期:函数结束时变量被自动销毁;
如果函数中有局部变量,其名称同本地变量无关;
在函数中定义局部变量的方法:
local NAME=VALUE
函数递归:函数直接或间接调用自身;
#### Example 求 N 的阶乘
```
N!=N(N-1)(N-2)….1
N(N-1)!=N(N-1)(N-2)!
```
```
#!/bin/bash
#
#利用函数递归求 N 的阶乘
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ];then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact 5
```
## 9 数组
### 数组
> * 变量:存储单个元素的内存空间;
> * 数组:存储多个元素的连续的内存空间;
> * 索引:编号从 0 开始,属于数值索引;
> * 注意:索引页可支持使用自定义的格式,而不仅仅是数值格式;bash 的数组支持稀疏格式;
#### 定义
在 Shell 中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为:
```
array_name=(value1 ... valuen)
```
例如:
```
array_name=(value0 value1 value2 value3)
```
或者
```
array_name=(
value0
value1
value2
value3
)
```
还可以单独定义数组的各个分量:
```
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
```
**Example 生成 10 个随机数保存于数组中,并找出其最大值和最小值**
```
#!/bin/bash
#
#生成 10 个随机数保存于数组中;
declare -a rand
declare -i max=0
declare -i min=0
for i in {0..9};do
rand[$i]=$RANDOM
if [ $i -eq 1 ];then
max=${rand[$i]}
min=${rand[$i]}
fi
echo ${rand[$i]}
[ ${rand[$i]} -gt $max ] && max=${rand[$i]}
[ ${rand[$i]} -lt $min ] && min=${rand[$i]}
done
echo "Max:$max"
echo "Min:$min"
```
**Example 定义一个数组,数组中的元素是 /var/log 目录下所有以.log 结尾的文件;要统计其下标为偶数的文件中的行数之和**
```
#!/bin/bash
#
#定义一个数组,数组中的元素是 /var/log 目录下所有以.log 结尾的文件;
#要统计其下标为偶数的文件中的行数之和;
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]);do
if [ $[$i%2] -eq 0 ];then
let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)
fi
done
echo "Lines:$lines."
```
### 引用数组中的元素
- 所有元素:${ARRAY[@]} , ${ARRAY[*]}
- 数组切片:${ARRAY[@]:offset:number}
offset: 要跳过的元素个数;
number:要取出的元素个数,取偏移量之后的所有元素:${ARRAY[@]:offset};
- 向数组中追加元素:ARRAY[${ARRAY[*]}]
- 删除数组中的某元素:unset ARRAY[INDEX]
- 关联数组:
declare –A ARRAY_NAME
ARRAY_NAME=([index_name1]=’val1’ [index_name2]=’val2’ ….)
```
[root@bill scripts]# declare -a array
[root@bill scripts]# #声明一个数组,不是必要的
[root@bill scripts]# array=(0 1 2)
[root@bill scripts]# array=([0]=0 [1]=1 [2]=v2)
[root@bill scripts]# array[0]=5
[root@bill scripts]# echo $array
5
```
```
[root@bill scripts]# #以空白作为分隔符拆分字符串为数组
[root@bill scripts]# str="1 2 3"
[root@bill scripts]# array=($str)
[root@bill scripts]# echo $array
1
```
```
[root@bill scripts]# #使用其他分隔符拆分字符串为数组,需指定 IFS
[root@bill scripts]# IFS=: array=($PATH)
[root@bill scripts]# echo $array
/usr/local/sbin
```
- 引用数组元素:
$array ${array} ${array[0]} #第 0 个元素
${array[n]} #第 n 个元素(n 从 0 开始计算)
- 引用整个数组:
${array[*]} ${array[@]} 这两种方式等同,会把数组展开。
${array[*]} 表示把数组拼接在一起的整个字符串,如果作为参数传递,会把整个字符串作为一个参数。
${array[@]} 如果作为参数传递,表示把数组中每个元素作为一个参数,数组有多少个元素,就会展开成多少个参数。
- 计算数组元素长度;
${#array[*]}
${#array[@]}
不是 ${#array},
因为它等同于 ${#array[0]}
- 遍历数组:
for i in "${array[@]}";do
echo $i;
done
## 10 bash 的字符串处理工具
### 字符串切片
- ${var:offset:number}
取字符串最右侧的几个字符:${var: -lengh}
Note:冒号后面必须有一空白字符;
### 基于模式取子串
- ${var#*word}:其中 word 可以是指定的任意字符;
功能:自左而右,查找 var 变量所存储的字符串中,第一次出现的 word, 删除字符串开头至第一次出现 word 字符之间的所有字符;
- ${var##*word}:其中 word 可以是指定的任意字符;
功能:自左而右,查找 var 变量所存储的字符串中出现的 word,删除字符串开头至最后一次由 word 指定的字符之间的所有内容;
**bash 基于模式取子串**
```
[root@bill scripts]# file="/var/log/messages"
[root@bill scripts]# echo ${file#*/}
var/log/messages
[root@bill scripts]# echo ${file##*/}
messages
```
- ${var%word*}:其中 word 可以是指定的任意字符;
功能:自右而左,查找 var 变量所存储的字符串中,第一次出现的 word, 删除字符串最后一个字符向左至第一次出现 word 字符之间的所有字符;
- ${var%%word*}:其中 word 可以是指定的任意字符;
功能:自右而左,查找 var 变量所存储的字符串中出现的 word,,只不过删除字符串最右侧的字符向左至最后一次出现 word 字符之间的所有字符;
```
[root@bill scripts]# file="/var/log/messages" #从右至左,匹配‘/ ’
[root@bill scripts]# echo ${file%/*}
/var/log
[root@bill scripts]# echo ${file%%/*} # 双 % 匹配并删除后为空;
[root@bill scripts]#
```
**Exampleurl=http://www.google.com:80, 分别取出协议和端口**
```
[root@bill scripts]# url=http://www.google.com:80
[root@bill scripts]# echo ${url##*:} #取端口号
80
[root@bill scripts]# echo ${url%%:*} #取协议
http
[root@bill scripts]#
```
### 查找替换
${var/pattern/substi}:查找 var 所表示的字符串中,第一次被 pattern 所匹配到的字符串,以 substi 替换之;
${var//pattern/substi}: 查找 var 所表示的字符串中,所有能被 pattern 所匹配到的字符串,以 substi 替换之;
${var/#pattern/substi}:查找 var 所表示的字符串中,行首被 pattern 所匹配到的字符串,以 substi 替换之;
${var/%pattern/substi}:查找 var 所表示的字符串中,行尾被 pattern 所匹配到的字符串,以 substi 替换之;
```
[root@bill scripts]# var=$(head -n 1 /etc/passwd)
[root@bill scripts]# echo $var
root x 0 0 root /root /bin/bash
[root@bill scripts]# echo ${var/root/ROOT} #替换第一次匹配到的 root 为 ROOT
ROOT x 0 0 root /root /bin/bash
[root@bill scripts]# echo ${var//root/ROOT} #替换所有匹配到的 root 为 ROOT
ROOT x 0 0 ROOT /ROOT /bin/bash
```
```
[root@bill scripts]# useradd bash -s /bin/bash
[root@bill scripts]# cat /etc/passwd | grep "^bash.*bash$"
bash:x:1029:1029::/home/bash:/bin/bash
[root@bill scripts]# line=$(cat /etc/passwd | grep "^bash.*bash$")
[root@bill scripts]# echo $line
bash x 1029 1029 /home/bash /bin/bash
[root@bill scripts]# echo ${line/#bash/BASH} #替换行首的 bash 为 BASH
BASH x 1029 1029 /home/bash /bin/bash
[root@bill scripts]# echo ${line/%bash/BASH} #替换行尾的 bash 为 BASH
bash x 1029 1029 /home/bash /bin/BASH
```
### 查找并删除
${var/pattern}:查找 var 所表示的字符串中,删除第一次被 pattern 所匹配到的字符串
${var//pattern}:查找 var 所表示的字符串中,删除所有被 pattern 所匹配到的字符串;
${var/#pattern}:查找 var 所表示的字符串中,删除行首被 pattern 所匹配到的字符串;
${var/%pattern}:查找 var 所表示的字符串中,删除行尾被 pattern 所匹配到的字符串;
- Example
```
[root@bill scripts]# line=$(tail -n 1 /etc/passwd)
[root@bill scripts]# echo $line
bash x 1029 1029 /home/bash /bin/bash
[root@bill scripts]# echo ${line/bash} #查找并删除第一次匹配到的 bash
x 1029 1029 /home/bash /bin/bash
[root@bill scripts]# echo ${line//bash} #查找并删除所有匹配到的 bash
x 1029 1029 /home/ /bin/
[root@bill scripts]# echo ${line/#bash} #查找并删除匹配到的行首的 bash
x 1029 1029 /home/bash /bin/bash
[root@bill scripts]# echo ${line/%bash} #查找并删除匹配到的行尾 bash
bash x 1029 1029 /home/bash /bin/
```
### 字符大小写转换
${var^^}:把 var 中的所有小写字母转换为大写;
${var,,}:把 var 中的所有大写字母转换为小写;
- Example
```
[root@bill scripts]# line=$(tail -n 1 /etc/fstab) #将文件最后一行的值赋值给变量
[root@bill scripts]# echo ${line^^} #全部转换为大写后输出
/DEV/MAPPER/CENTOS-SWAP SWAP SWAP DEFAULTS 0 0
[root@bill scripts]# line=`echo ${line^^}` #将转换为大写后的值在赋值给变量;
[root@bill scripts]# echo $line #确认目前变量的值全为大写字母;
/DEV/MAPPER/CENTOS-SWAP SWAP SWAP DEFAULTS 0 0
[root@bill scripts]# echo ${line,,} #全部转换为大写后输出;
/dev/mapper/centos-swap swap swap defaults 0 0
```
### 变量赋值
${var:-value}:如果 var 为空或未设置,那么返回 value;否则,则返回 var 的值;
${var:=value}:如果 var 为空或未设置,那么返回 value,并将 value 赋值给 var;否则,则返回 var 的值;
- Example
```
[root@bill scripts]# echo $test #变量值为空;
[root@bill scripts]# echo ${test:-helloworld} # :- 仅返回设定值,不修改;
helloworld
[root@bill scripts]# echo $test #变量的值依然为空;
[root@bill scripts]# echo ${test:=helloworld} # := 返回设定值,并赋值给变量;
helloworld
[root@bill scripts]# echo $test # 变量值已修改;
helloworld
```
## 11 Bash 命令自动补全
### 11.1 内置补全命令
Bash 内置有两个补全命令,分别是 compgen 和 complete。compgen 命令根据不同的参数,生成匹配单词的候选补全列表,例如:
```
$ compgen -W 'hi hello how world' h
hi
hello
how
```
compgen 最常用的选项是 -W,通过 -W 参数指定空格分隔的单词列表。h 即我们在命令行当前键入的单词,执行完后会输出候选的匹配列表,这里是以 h 开头的所有单词。
complete 命令的参数有点类似 compgen,不过它的作用是说明命令如何进行补全,例如同样使用 -W 参数指定候选的单词列表:
```
$ complete -W 'word1 word2 word3 hello' foo
$ foo w<Tab>
$ foo word<Tab>
word1 word2 word3
```
我们还可以通过 -F 参数指定一个补全函数:
```
$ complete -F _foo foo
```
现在键入 foo 命令后,会调用_foo 函数来生成补全的列表,完成补全的功能,这一点正是补全脚本实现的关键所在,我们会在后面介绍。
补全相关的内置变量
除了上面的两个补全命令外,Bash 还有几个内置的变量用来辅助补全功能,这里主要介绍其中三个:
> * COMP_WORDS: 类型为数组,存放当前命令行中输入的所有单词;
> * COMP_CWORD: 类型为整数,当前光标下输入的单词位于 COMP_WORDS 数组中的索引;
> * COMPREPLY: 类型为数组,候选的补全结果;
> * COMP_WORDBREAKS: 类型为字符串,表示单词之间的分隔符;
> * COMP_LINE: 类型为字符串,表示当前的命令行输入;
例如我们定义这样一个补全函数_foo:
```
$ function _foo()
{
echo -e "\n"
declare -p COMP_WORDS
declare -p COMP_CWORD
declare -p COMP_LINE
declare -p COMP_WORDBREAKS
}
$ complete -F _foo foo
```
假设我们在命令行下输入以下内容,再按下 Tab 键补全:
```
$ foo b
declare -a COMP_WORDS='([0]="foo" [1]="b")'
declare -- COMP_CWORD="1"
declare -- COMP_LINE="foo b"
declare -- COMP_WORDBREAKS="
\"'><=;|&(:"
```
对着上面的结果,我想应该比较容易理解这几个变量。当然正如我们之前据说,Bash-completion 包并非是必须的,补全功能是 Bash 自带的。
### 11.2 编写脚本
补全脚本分成两个部分:编写一个补全函数和使用 complete 命令应用补全函数。后者的难度几乎忽略不计,重点在如何写好补全函数。难点在,似乎网上很少与此相关的文档,但是事实上,Bash-completion 自带的补全脚本是最好的起点,可以挑几个简单的改改基本上就可以使用了。
一般补全函数(假设这里依然为_foo) 都会定义以下两个变量:
```
local cur prev
```
其中 cur 表示当前光标下的单词,而 prev 则对应上一个单词:
```
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
```
#### 11.2.1 支持主选项
初始化相应的变量后,我们需要定义补全行为,即输入什么的情况下补全什么内容,例如当输入 - 开头的选项的时候,我们将所有的选项作为候选的补全结果:
```
local opts="-h --help -f --file -o --output"
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
```
不过再给 COMPREPLY 赋值之前,最好将它重置清空,避免被其它补全函数干扰。
现在完整的补全函数是这样的:
```
function _foo() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-h --help -f --file -o --output"
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
```
现在在命令行下就可以对 foo 命令进行参数补全了:
```
$ complete -F _foo foo
$ foo -
-f --file -h --help -o --output
```
#### 11.2.2 支持子选项
当然,似乎我们这里的例子没有用到 prev 变量。用好 prev 变量可以让补全的结果更加完整,例如当输入 --file 之后,我们希望补全特殊的文件(假设以.sh 结尾的文件):
```
case "${prev}" in
-f|--file)
COMPREPLY=( $(compgen -o filenames -W "`ls *.sh`" -- ${cur}) )
;;
esac
```
现在再执行 foo 命令,--file 参数的值也可以补全了:
```
$ foo --file<Tab>
a.sh b.sh c.sh
```
#### 11.2.3 安装补全脚本
如果安装了 Bash-completion 包,可以将补全脚本放在 /etc/bash_completion.d 目录下,或者放到~/.bash_completion 文件中。
如果没有安装 Bash-completion 包,可以把补全脚本放到~/.bashrc 或者其它能被 shell 加载的初始化文件中。
## 12 常用实例
### 12.1 推荐添加内容
```
set -u # 确保变量都被初始化
set -e # 确保捕获所有非 0 状态
set -o pipefail # 结合 -e 可以捕获管道后的异常状态
```
### 12.2 脚本的配置文件
- (1) 定义文本文件,每行定义"name=value"
- (2) 在脚本中 source 此文件即可
```
[root@bill scripts]# touch /tmp/config.test #创建配置文件;
[root@bill scripts]# echo "name=bill" >> /tmp/config.test #在配置文件中定义变量;
[root@bill scripts]# vim script_configureFile.sh #编写脚本,导入配置文件;如内容所示;
[root@bill scripts]# bash script_configureFile.sh #脚本执行结果;
bill
[root@bill scripts]# cat script_configureFile.sh
#!/bin/bash
#
source /tmp/config.test #导入配置文件,脚本自身并未定义变量;
echo $name #引用的是配置文件中的变量 name
```
### 12.3 ssh 登录相关
> * 可以使用 sshpass 进行直接传入密码
> * 一定要加 -o StrictHostKeyChecking=no 否则如果是第一次访问对应的机器,会执行无效
```
ssh_option="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 -o ConnectTimeout=3 -o ConnectionAttempts=3"
export WSSH="./tools/sshpass -p ${PASSWD} ssh ${ssh_option}"
export WSCP="./tools/sshpass -p ${PASSWD} scp ${ssh_option}"
```
> * StrictHostKeyChecking=no 自动信任主机并添加到known_hosts文件
> * UserKnownHostsFile=/dev/null 跳过检查 ~/.ssh/known_hosts 中的公钥操作,比如因为远端机器重装导致无法登录问题
> * NumberOfPasswordPrompts=0 规避没有信任关系挂死的问题,当对应的机器需要输入密码时,会直接返回异常(异常返回码为 255),而不是阻塞在输入密码页面
> * ConnectTimeout=3 连接超时时间,3秒
> * ConnectionAttempts=3 连接失败后重试次数,3次
### 12.4 ping 文件列表中所有主机
```
#!/bin/bash
file=$1
[[ -z $file ]] && exit 0
cat $file| while read line;do
ping=`ping -c 1 $line|grep loss|awk '{print $6}'|awk -F "%" '{print $1}'`
if [ $ping -eq 100 ];then
echo ping $i fail
else
echo ping $i ok
fi
done
```
### 12.5 shell 模板变量替换
#### 应用场景
双引号是 json 的标准,如果一个服务的配置文件是 json 的,使用特定的配置(模板)生成对应的配置文件时。要是使用 shell,这样也可以做到:
#### 使用方式
模板文件
```
{
"test_ip" : ${test_ip},
"test_port" : ${test_port},
}
```
脚本
```
test_host="127.0.0.1"
test_port=9099
content=$(cat ./config_tpl)
content_new=$(eval "cat <<EOF
$content
EOF")
echo $content_new
```
## 13 日常使用库
```
f_yellow='\e[00;33m'
f_red='\e[00;31m'
f_green='\e[00;32m'
f_reset='\e[00;0m'
function p_warn {
echo -e "${f_yellow}[WRN]${f_reset} ${1}"
}
function p_err {
echo -e "${f_red}[ERR]${f_reset} ${1}"
}
function p_ok {
echo -e "${f_green}[OK ]${f_reset} ${1}"
}
ROOT_PATH=`S=\`readlink "$0"\`; [ -z "$S" ] && S=$0; dirname $S`
cd ${ROOT_PATH}
ssh_option="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 -o ConnectTimeout=3 -o ConnectionAttempts=3"
```
================================================
FILE: doc/Linux/tools.md
================================================
# Linux 工具篇
<!-- vim-markdown-toc GFM -->
* [1 编程相关](#1-编程相关)
* [1.1 vim IDE 工具](#11-vim-ide-工具)
* [1.2 Git 分布式管理系统](#12-git-分布式管理系统)
* [1.2.1 Git 基础](#121-git-基础)
* [1.2.2 知识点](#122-知识点)
* [1.2.3 其他操作](#123-其他操作)
* [**解决 GitHub commit 次数过多.git 文件过大**](#解决-github-commit-次数过多git-文件过大)
* [**HTTP request failed**](#http-request-failed)
* [设置 Wiki 只能自己编写,其他人员只读](#设置-wiki-只能自己编写其他人员只读)
* [修改最后一次 commit 操作](#修改最后一次-commit-操作)
* [1.2.4 Git tips](#124-git-tips)
* [2 运维相关](#2-运维相关)
* [2.1 sed](#21-sed)
* [2.2 awk](#22-awk)
* [2.2.1 基础知识](#221-基础知识)
* [2.2.2 脚本](#222-脚本)
* [2.2.3 运算与编程](#223-运算与编程)
* [2.2.4 AWK 中输出外部变量](#224-awk-中输出外部变量)
* [2.2.5 AWK if](#225-awk-if)
* [2.2.6 AWK 打印第 N 列后面的所有列](#226-awk-打印第-n-列后面的所有列)
* [2.3 find](#23-find)
* [2.3.1 linux 文件查找指定时间段的文件](#231-linux-文件查找指定时间段的文件)
* [2.4 grep](#24-grep)
* [2.4.1 grep 时出现错误 Binary file (standard input) matches](#241-grep-时出现错误-binary-file-standard-input-matches)
* [2.5 sshpass 命令行使用密码来进行远程操作](#25-sshpass-命令行使用密码来进行远程操作)
* [3 系统相关](#3-系统相关)
* [3.1 screen](#31-screen)
* [3.1.1 screen 使用](#311-screen-使用)
* [3.1.2 开启 screen 状态栏](#312-开启-screen-状态栏)
* [3.2 dmesg](#32-dmesg)
* [3.3 日志切割之 Logrotate](#33-日志切割之-logrotate)
* [3.3.1 通用服务日志清理工具](#331-通用服务日志清理工具)
* [3.4 lsof](#34-lsof)
* [3.4.1 如何使用 lsof?](#341-如何使用-lsof)
* [3.4.2 lsof -p](#342-lsof--p)
* [4 网络相关](#4-网络相关)
* [4.1 curl](#41-curl)
* [4.1.1 HTTP 请求](#411-http-请求)
* [4.1.2 curl 基础](#412-curl-基础)
* [4.1.3 curl 深入](#413-curl-深入)
* [4.2 tcpdump](#42-tcpdump)
* [4.2.1 tcp 三次握手和四次挥手](#421-tcp-三次握手和四次挥手)
* [4.2.2 tcpdump 使用](#422-tcpdump-使用)
* [4.3 nc](#43-nc)
* [4.3.1 语法](#431-语法)
* [4.3.2 TCP 端口扫描](#432-tcp-端口扫描)
* [4.3.3 扫描 UDP 端口](#433-扫描-udp-端口)
* [5 日志相关操作](#5-日志相关操作)
* [5.1 截取某段时间内的日志](#51-截取某段时间内的日志)
* [5.2 处理日志文件中上下关联的两行](#52-处理日志文件中上下关联的两行)
* [6 应用服务相关](#6-应用服务相关)
* [6.1 排查 java CPU 性能问题](#61-排查-java-cpu-性能问题)
* [6.1.1 用法](#611-用法)
* [6.1.2 示例](#612-示例)
* [7 日常工具](#7-日常工具)
* [7.1 mget](#71-mget)
<!-- vim-markdown-toc -->
# 1 编程相关
## 1.1 vim IDE 工具
* [VIM 一键 IDE](https://github.com/meetbill/Vim)
## 1.2 Git 分布式管理系统
### 1.2.1 Git 基础
**环境配置**
+ `git config --global user.name your_name` : 设置你的用户名,提交会显示
+ `git config --global user.email your_email` : 设置你的邮箱
+ `git config core.quotepath false` : 解决中文文件名显示为数字问题
**基本操作**
+ `git init` : 初始化一个 git 仓库
+ `git add <filename>` : 添加一个文件到 git 仓库中
+ `git commit -m "commit message"`: 提交到本地
+ `git push [remote-name] [branch-name]` : 把本地的提交记录推送到远端分支
+ `git pull`: 更新仓库 `git pull` = `git fetch` + `git merge`
+ `git checkout -- <file>` : 还原未暂存 (staged) 的文件
+ `git reset HEAD <file>...` : 取消暂存,那么还原一个暂存文件,应该是先 `reset` 后 `checkout`
+ `git stash` : 隐藏本地提交记录,恢复的时候 `git stash pop`。这样可以在本地和远程有冲突的情况下,更新其他文件
**分支**
+ `git branch <branch-name>` : 基于当前 commit 新建一个分支,但是不切换到新分支
+ `git branch -r` : 查看远程的所有分支(常用)
+ `git checkout -b <branch-name>` : 新建并切换分支
+ `git checkout <branch-name>` : 切换分支(常用)
+ `git branch -d <branch-name>` : 删除分支
+ `git push origin <branch-name>` : 推送本地分支
+ `git checkout -b <local-branch-name> origin/<origin-branch-name>` : 基于某个远程分支新建一个分支开发
+ `git checkout --track origin/<origin-branch-name>` : 跟踪远程分支(创建跟踪远程分支,Git 在 `git push` 的时候不需要指定 `origin` 和 `branch-name` ,其实当我们 `clone` 一个 repo 到本地的时候,`master` 分支就是 origin/master 的跟踪分支,所以提交的时候直接 `git push`)。
+ `git push origin :<origin-branch-name>` : 删除远程分支
实践 --- 主分支 Master/ 开发分支 Develop
```
主分支只用来分布重大版本,日常开发应该在另一条分支上完成。我们把开发用的分支,叫做 Develop。
# Git 创建 Develop 分支
git checkout -b develop master
# 将 Develop 分支发布到 Master 分支
# 切换到 Master 分支
git checkout master
# 对 Develop 分支进行合并
git merge --no-ff develop
上一条命令的 --no-ff 参数是什么意思。默认情况下,Git 执行"快进式合并"(fast-farward merge),会直接将 Master 分支指向 Develop 分支。
# 删除本地分支
git branch -d develop
```
**标签**
+ `git tag -a <tagname> -m <message>` : 创建一个标签(用 -a 指定标签名,-m 指定说明文字) 如 `git tag -a v1.0 -m "version 1.0 released mitaka"`
+ `git tag` : 显示已有的标签
+ `git show tagname`: 显示某个标签的详细信息
+ `git push origin v1.0` push 到远端仓库 如`git push -u ${PWD##*/} master v1.0`
+ `git checkout -b <tag-name>` : 基于某个 tag 创建一个新的分支
**Git shortcuts/aliases**
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
### 1.2.2 知识点
基本命令让你快速的上手使用 Git,知识点能让你更好的理解 Git。
**文件的几种状态**
+ untracked: 未被跟踪的,没有纳入 Git 版本控制,使用 `git add <filename>` 纳入版本控制
+ unmodified: 未修改的,已经纳入版本控制,但是没有修改过的文件
+ modified: 对纳入版本控制的文件做了修改,git 将标记为 modified
+ staged: 暂存的文件,简单理解:暂存文件就是 add 之后,commit 之前的文件状态
理解这几种文件状态对于理解 Git 是非常关键的(至少可以看懂一些错误提示了)。
**快照和差异**
详细可看:[Pro Git: Git 基础](http://iissnan.com/progit/html/zh/ch1_3.html) 中有讲到 *直接记录快照,而非差异比较*,这里只讲我个人的理解。
Git 关心的是文件数据整体的变化,其他版本管理系统(以 svn 为例)关心的某个具体文件的*差异*。这个差异是好理解的,也就是两个版本具体文件的不同点,比如某一行的某个字符发生了改变。
Git 不保存文件提交前后的差异,不变的文件不会发生任何改变,对于变化的文件,前后两次提交则保存两个文件。举个例子:
SVN:
1. 新建 3 个文件 a, b, c,做第一次提交 -> `version1 : file_a file_b file_c`
2. 修改文件 b, 做第二次提交(真正提交的是 修改后的文件 b 和修改前的 `file_b` 的 diff) -> `version2: diff_b_2_1`
3. 当我要 checkout version2 的时候,实际上得到的是 `file_a file_b+diff_b_2_1 file_c`
Git:
1. 新建 3 个文件 a, b, c,做第一次提交 -> `version1 : file_a file_b file_c`
2. 修改文件 b (得到`file_b1`), 做第二次提交 -> `version2: file_a file_b1 file_c`
3. 当我要用 version2 的时候,实际上得到的是 `file_a file_b1 file_c`
上面的 `file_a file_b1 file_c` 就是 version2 的 *快照*。
**Git 数据结构**
Git 的核心数是很简单的,就是一个链表(或者一棵树更准确一些?无所谓了),一旦你理解了它的基本数据结构,再去看 Git,相信你有不同的感受。继续用上面的例子(所有的物理文件都对应一个 SHA-1 的值)
当我们做第一次提交时,数据结构是这样的:
sha1_2_file_map:
28415f07ca9281d0ed86cdc766629fb4ea35ea38 => file_a
ed5cfa40b80da97b56698466d03ab126c5eec5a9 => file_b
1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39 => file_c
commit_26b985d269d3a617af4064489199c3e0d4791bb5:
base_info:
Auther: "JerryZhang(chinajiezhang@gmail.com)"
Date: "Tue Jul 15 19:19:22 2014 +0800"
commit_content: "第一次提交"
file_list:
[1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38
[2]: ed5cfa40b80da97b56698466d03ab126c5eec5a9
[3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39
pre_commit: null
next_commit: null
当修改了 `file_b`, 再提交一次时,数据结构应该是这样的:
sha1_2_file_map:
28415f07ca9281d0ed86cdc766629fb4ea35ea38 => file_a
ed5cfa40b80da97b56698466d03ab126c5eec5a9 => file_b
1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39 => file_c
39015ba6f80eb9e7fdad3602ef2b1af0521eba89 => file_b1
commit_26b985d269d3a617af4064489199c3e0d4791bb5:
base_info:
Auther: "JerryZhang(chinajiezhang@gmail.com)"
Date: "Tue Jul 15 19:19:22 2014 +0800"
commit_content: "第一次提交"
file_list:
[1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38
[2]: ed5cfa40b80da97b56698466d03ab126c5eec5a9
[3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39
pre_commit: commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff
next_commit: null
commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff:
base_info:
Auther: "JerryZhang(chinajiezhang@gmail.com)"
Date: "Tue Jul 15 22:19:22 2014 +0800"
commit_content: "更新文件 b"
file_list:
[1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38
[2]: 39015ba6f80eb9e7fdad3602ef2b1af0521eba89
[3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39
pre_commit: null
next_commit: commit_26b985d269d3a617af4064489199c3e0d4791bb5
当提交完第二次的时候,执行 `git log`,实际上就是从 `commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff` 开始遍历然后打印 `base_info` 而已。
实际的 git 实际肯定要比上面的结构 ((的信息)的)要复杂的多,但是它的核心思想应该是就是,每一次提交就是一个新的结点。通过这个结点,我可以找到所有的快照文件。再思考一下,什么是分支?什么是 Tags,其实他们可能只是某次提交的引用而已(一个 `tag_head_node` 指向了某一次提交的 node)。再思考怎么回退一个版本呢?指针偏移!依次类推,上面的基本命令都可以得到一个合理的解释。
**理解 git fetch 和 git pull 的差异**
上面我们说过 `git pull` 等价于 `git fetch` 和 `git merge` 两条命令。当我们 `clone` 一个 repo 到本地时,就有了本地分支和远端分支的概念(假定我们只有一个主分支),本地分支是 `master`,远端分支是 `origin/master`。通过上面我们对 Git 数据结构的理解,`master` 和 `origin/master` 可以想成是指向最新 commit 结点的两个指针。刚 `clone` 下来的 repo,`master` 和 `origin/master` 指针指向同一个结点,我们在本地提交一次,`origin` 结点就更新一次,此时 `master` 和 `orgin/master` 就不再相同了。很有可能别人已经 commit 改 repo 很多次了,并且进行了提交。那么我们的本地的 `origin/master` 就不再是远程服务器上的最新的位置了。 `git fetch` 干的就是从服务器上同步服务器上最新的 `origin/master` 和一些服务器上新的记录 / 文件到本地。而 `git merge` 就是合并操作了(解决文件冲突)。`git push` 是把本地的 `origin/master` 和 `master` 指向相同的位置,并且推送到远程的服务器。
### 1.2.3 其他操作
#### **解决 GitHub commit 次数过多.git 文件过大**
完全重建版本库
```
# rm -rf .git
# git init
# git add .
# git commit -a -m "[Update] 合并之前所有 commit"
# git remote add origin <your_github_repo_url>
# git push -f -u origin master
```
#### **HTTP request failed**
使用 git clone 失败
```
[root@localhost ~]# git clone https://github.com/meetbill/Vim.git
Initialized empty Git repository in /root/Vim/.git/
error: while accessing https://github.com/meetbill/Vim.git/info/refs
fatal: HTTP request failed
```
解决方法
```
#git config --global http.sslVerify false
```
#### 设置 Wiki 只能自己编写,其他人员只读
在项目中的设置中勾选`Restrict editing to collaborators only`
#### 修改最后一次 commit 操作
有时候我们提交完了才发现漏掉了几个文件没有加,或者提交信息写错了。想要撤消刚才的提交操作,可以使用 --amend 选项重新提交:
```
$ git commit -a -m 'initial commit'
$ git add forgotten_file
$ git commit --amend -m 'new commit'
```
### 1.2.4 Git tips
> 命令简化
```
cd git_repo(替换为项目名字)
git remote add ${PWD##*/} git@github.com:meetbill/${PWD##*/}.git
git push -u ${PWD##*/} master
```
> 提升 git 使用体验
> * [git 命令自动补全](https://github.com/meetbill/op_practice_code/tree/master/Linux/tools/git/git-completion)
> * [命令行显示 git 信息](https://github.com/meetbill/op_practice_code/tree/master/Linux/tools/git/git-ps1)
> 忽略特殊文件
```
.gitignore 写得有问题,需要找出来到底哪个规则写错了,可以用 git check-ignore 命令检查:
$ git check-ignore -v App.class
.gitignore:3:*.class App.class
.gitignore 的第 3 行规则忽略了该文件,于是我们就可以知道应该修订哪个规则。
```
# 2 运维相关
## 2.1 sed
## 2.2 awk
AWK 是贝尔实验室 1977 年搞出来的文本处理工具。
之所以叫 AWK 是因为其取了三位创始人 Alfred Aho,Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符
### 2.2.1 基础知识
**分隔符**
默认情况下, awk 使用空格当作分隔符。分割后的字符串可以使用 $1, $2 等访问。
上面提到过,我们可以使用 -F 来指定分隔符。
fs 如果是一个字符,可以直接跟在 -F 后面,比如使用冒号当作分隔符就是 -F: .
如果分隔符比较复杂,就需要使用正则表达式来表示这个分隔符了。
正则表达式需要使用引号引起来。
比如使用‘ab’ 当作分隔符,就是 -F 'ab' 了。
使用 a 或 b 作为分隔符,就是 -F '\[ab]' 了。
关于正则表达式这里不多说了。
**内建变量**
```text
$0 当前记录(这个变量中存放着整个行的内容)
$1~$n 当前记录的第 n 个字段,字段间由 FS 分隔
FS 输入字段分隔符 默认是空格或 Tab
NF 当前记录中的字段个数,就是有多少列
NR 已经读出的记录数,就是行号,从 1 开始,如果有多个文件话,这个值也是不断累加中。
FNR 当前记录数,与 NR 不同的是,这个值会是各个文件自己的行号
RS 输入的记录分隔符, 默认为换行符
OFS 输出字段分隔符, 默认也是空格
ORS 输出的记录分隔符,默认为换行符
FILENAME 当前输入文件的名字
```
**转义**
一般字符在双引号之内就可以直接原样输出了。
但是有部分转义字符,需要使用反斜杠转义才能正常输出。
```
\\ A literal backslash.
\a The “alert” character; usually the ASCII BEL character.
\b backspace.
\f form-feed.
\n newline.
\r carriage return.
\t horizontal tab.
\v vertical tab.
\xhex digits
\c The literal character c.
```
**模式**
```
~ 表示模式开始,与 == 相比不是精确比较
/ / 中是模式
! 模式取反
```
**单引号**
当需要输出单引号时,直接转义发现会报错。
由于 awk 脚本并不是直接执行,而是会先进行预处理,所以需要两次转义。
awk 支持递归引号。单引号内可以输出转义的单引号,双引号内可以输出转义的双引号。
比如需要输出单引号,则需要下面这样:
```
> awk 'BEGIN{print "\""}'
"
> awk 'BEGIN{print "'\''"}'
'
```
当然,更简单的方式是使用十六进制来输出。
```
awk 'BEGIN{print "\x27"}'
```
### 2.2.2 脚本
```
BEGIN{ 这里面放的是执行前的语句 }
END {这里面放的是处理完所有的行后要执行的语句 }
{这里面放的是处理每一行时要执行的语句}
```
### 2.2.3 运算与编程
awk 是弱类型语言,变量可以是串,也可以是数字,这依赖于实际情况。所有的数字都是浮点型。
例如
```
//9
echo 5 4 | awk '{ print $1 + $2 }'
//54
echo 5 4 | awk '{ print $1 $2 }'
//"5 4"
echo 5 4 | awk '{ print $1, $2 }'
0-1-2-3-4-5-6
echo 6 | awk '{ for (i=0; i<=$0; i++){ printf (i==0?i:"-"i); }printf "\n";}'
```
**Example**
假设我们有一个日期 2014/03/27, 我们想处理为 2014-03-27.
我们可以使用下面的代码实现。
```bash
echo "2014/03/27" | awk -F/ '{print $1"-"$2"-"$3}'
```
假设 处理的日期都在 date 文件里。
我们可以导入文件来操作
文件名 date
```
2014/03/27
2014/03/28
2014/03/29
```
命令
```
awk -F/ '{printf "%s-%s-%s\n",$1,$2,$3}' date
```
输出
```text
2014-03-27
2014-03-28
2014-03-29
```
**统计**
```bash
awk '{sum+=$5} END {print sum}'
```
### 2.2.4 AWK 中输出外部变量
通过双引号内加个单引号将外部变量进行输出
```
wang="hello"
echo meetbill | awk '{print "'$wang' " $1}'
```
### 2.2.5 AWK if
必须用在{}中,且比较内容用 () 扩起来
```
awk -F: '{if($1~/mail/) print $1}' /etc/passwd // 简写
awk -F: '{if($1~/mail/) {print $1}}' /etc/passwd // 全写
awk -F: '{if($1~/mail/) {print $1} else {print $2}}' /etc/passwd //if...else...
```
> * 条件表达式
> * `== != > >=`
> * 逻辑运算符
> * `&& ||`
如:查看使用了 CPU 0 核的进程
```
# ps -eF,其中 PSR 就是 (processor that process is currently assigned to.) 或者 ps -eo pid,command,args,psr
ps -eF |awk '{if($7==0) print $0}'
```
### 2.2.6 AWK 打印第 N 列后面的所有列
```
awk '{for(i=N+1;i<=NF;i++)printf $i " ";printf"\n"}' file
```
## 2.3 find
### 2.3.1 linux 文件查找指定时间段的文件
```
touch -t 201710241800 t1
touch -t 201710252100 t2
查找排序(先旧后新),结果写到文件
find ./ -type f -name "*.aof" -newer ./t1 ! -newer ./t2 |xargs ls -lrt > /sdcard/amr/sort.txt
```
## 2.4 grep
### 2.4.1 grep 时出现错误 Binary file (standard input) matches
在使用 grep 命令时出现错误 Binary file (standard input) matches
解决方法 加上 -a
例如原本为 grep hello
改为 grep -a hello
## 2.5 sshpass 命令行使用密码来进行远程操作
https://github.com/kevinburke/sshpass
```
例子:本地执行远程机器的命令:
命令: sshpass -p xxx ssh root@192.168.11.11 "ethtool eth0"
例子:从远程主机上拉取文件到本地
命令: sshpass -p xxx scp root@host_ip:/home/test/t ./tmp/
```
# 3 系统相关
## 3.1 screen
现在很多时候我们的开发环境都已经部署到云端了,直接通过 SSH 来登录到云端服务器进行开发测试以及运行各种命令,一旦网络中断,通过 SSH 运行的命令也会退出,这个发让人发疯的。
好在有 screen 命令,它可以解决这些问题。我使用 screen 命令已经有三年多的时间了,感觉还不错。
### 3.1.1 screen 使用
**新建一个 Screen Session**
```
$ screen -S screen_session_name
```
**将当前 Screen Session 放到后台**
```
$ CTRL + A + D
```
**唤起一个 Screen Session**
```
$ screen -r screen_session_name
```
**分享一个 Screen Session**
```
$ screen -x screen_session_name
```
通常你想和别人分享你在终端里的操作时可以用此命令。
**终止一个 Screen Session**
```
$ exit
$ CTRL + D
```
**查看一个 screen 里的输出**
当你进入一个 screen 时你只能看到一屏内容,如果想看之前的内容可以如下:
```
$ Ctrl + a ESC
```
以上意思是进入 Copy mode,拷贝模式,然后你就可以像操作 VIM 一样查看 screen session 里的内容了。
可以 Page Up 也可以 Page Down。
### 3.1.2 开启 screen 状态栏
```
#curl -o screen.sh https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/tools/screen.sh
#sh screen.sh
```
## 3.2 dmesg
内核缓冲信息,在系统启动时,显示屏幕上的与硬件有关的信息
> dmesg | grep -E 'error|fail'
```
dmesg - print or control the kernel ring buffer
dmesg is used to examine or control the kernel ring buffer.
The default action is to read all messages from kernel ring buffer.
```
> eth1: Too much work at interrupt, IntrStatus=0x0001
```
一般类的提示
某网卡的中断请求过多。如果只是偶尔出现一次可忽略
但这条提示如果经常出现或是集中出现,那涉及到的可能性就比较多有可能需要进行处理了。可能性比较多,如网卡性能;服务器性能;网络攻击.. 等等。
```
> IPVS: incoming ICMP: failed checksum from 61.172.0.X!
```
一般类的提示
服务器收到了一个校验和错误的 ICMP 数据包。这类的数据包有可能是非法产生的垃圾数据。但从目前来看服务器收到这样的数据非常多。一般都忽略。
一般代理服务器在工作时会每秒钟转发几千个数据包。收到几个错误数据包不会影响正常的工作。
```
> NET: N messages suppressed.
```
一般类的提示
服务器忽略了 N 个数据包。和上一条提示类似。服务器收到的数据包被认为是无用的垃圾数据数据。这类数据多是由攻击类的程序产生的。
这条提示如果 N 比较小的时候可以忽略。但如果经常或是长时间出现 3 位数据以上的这类提示。就很有可能是服务器受到了垃圾数据类的带宽攻击了。
与这条信息类似的还有。
__ratelimit: N messages suppressed
__ratelimit: N callbacks suppressed
```
> UDP: bad checksum. From 221.200.X.X:50279 to 218.62.X.X:1155 ulen 24
> UDP: short packet: 218.2.X.X:3072 3640/217 to 222.168.X.X:57596
> 218.26.131.X sent an invalid ICMP type 3, code 13 error to a broadcast: 0.1.0.4 on eth0
```
一般类的提示
服务器收到了一个错误的数据包。分别为 UDP 校验和错误;过短的 UDP 数据包;一个错误的 ICMP 类型数据。这类信息一般情况下也是非法产生的。
但一般问题不大可直接忽略。
```
> kernel: conntrack_ftp: partial 227 2205426703+13
> FTP_NAT: partial packet 2635716056/20 in 2635716048/2635716075
```
一般类的提示
服务器在维持一条 FTP 协议的连接时出错。这样的提示一般都可以直接忽略。
```
> NETDEV WATCHDOG: eth1: transmit timed out
```
eth1: link down
eth1: link up, 10Mbps, half-duplex, lpa 0x0000
eth2: link up, 100Mbps, full-duplex, lpa 0x41E1
setting full-duplex based on MII #24 link partner capability of 45e1
网络通信严重问题!
这些提示是网络通信中出现严重问题时才会出现。故障基本和网络断线有关系。这几条提示分别代表的含意是 某块网卡传送数据超时;网卡连接 down; 网卡连接 up, 连接速率为 10/100Mbps, 全 / 半双功。
这里写到的最后三行的提示比较类似。出现这类提示时必须注意网络连接状况进行处理!!!
```
> NIC Link is Up 100 Mbps Full Duplex
```
网络通信严重问题!
情况和 kernel: eth1: link up,... 相同。指某块网卡适应的连接速率。一般认为没有说明哪个网卡 down, 只是连续出现网卡适应速率也是通信有问题。
如果是网线正常的断接可以忽略这类的信息。
```
> eth0: Transmit timed out, status 0000, PHY status 786d, resetting...
> eth0: Reset not complete yet. Trying harder.
```
网络通信严重问题!
第一条提示 网卡关送数据失败。复位网卡。第二条提示 网卡复位不成功.... 这些提示都属于严重的通信问题。
报警程序的提示
0001 ##WMPCheckV001## 2005-04-13_10:10:01 Found .(ARP Spoofing sniffer)! IP:183 MAC:5
0002 ##WMPCheckV001## 2005-04-07_01:53:32 Found .(MAC_incomplete)! IP:173 mac_incomplete:186
0003 ##WMPCheckV001## 2005-04-17_16:25:11 Found .(HIGH_synsent)! totl:4271 SynSent:3490
0004 ##WMPCheckV001## 20......
这是由报警程序所引起的提示。详细的信息需要用报警程序的客户端进行实时接收。详细情况请查看"告警模块和日志".
```
> eth1: Promiscuous mode enabled.
> device eth1 entered promiscuous mode
> device eth1 left promiscuous mode
```
一般类的提示
这几行提示指。某块网卡进入(离开)了混杂模式。一般来说混杂模式是当需要对通信进行抓包时才用到的。当使用维护或故障分析时会使用到(比如 consoletools 中的 countflow 命令). 正常产生的这类提示可以忽略。
如果在前台和远端都没有进行维护时出现这个提示倒是应该引起注意,但这种可能性不大。
```
> keyboard: unknown scancode e0 5e
```
基本无关
键盘上接收到未定义的键值。如果经常出现。有可能是键盘有问题。linux 对于比较特殊的键或是组合键,有时也会出这样的提示。
要看一下服务器的键盘是不是被压住了。其它情况一般忽略。
```
> uses obsolete (PF_INET,SOCK_PACKET)
```
基本无关
系统内核调用了一部分功能模块,在第一次调入时会出现。一般情况与使用调试工具有关。可直接忽略。
```
> Neighbour table overflow.
```
网络通信故障
出现这个提示。一般都是因为局域网内有部分计算机被病毒感染。情况严重时会影响通信。必须处理内部网通信不正常的计算机。
```
> eth1: Transmit error, Tx status register 82.
```
Probably a duplex mismatch. See Documentation/networking/vortex.txt
Flags; bus-master 1, dirty 9994190(14) current 9994190(14)
Transmit list 00000000 vs. f7171580.
0: @f7171200 length 800001e6 status 000101e6
1: @f7171240 length 8000008c status 0001008c
这个提示是 3com 网卡特有的。感觉如果出现量不大的话也不会影响很严重。目前看维一的解决办法是更换服务器上的网卡。实在感觉 3com 的网卡有些问题...
```
> 服务器 CPU 工作温度过高
```
CPU0: Temperature above threshold
CPU0: Running in modulated clock mode
服务器系统严重故障
服务器 CPU 工作温度过高。必须排除硬件故障。
```
> 磁盘故障
```
I/O error, dev hda, sector N
I/O error, dev sda, sector N
hda: dma_intr: status=0x51 { DriveReady SeekComplete Error }
hda: dma_intr: error=0x40 { UncorrectableError }, LBAsect=811562, sector=811560
服务器系统严重故障
服务器系统磁盘存储卡操作失败。这样的问题一般不会使服务器直接停止工作,但会引起很多严重问题
```
## 3.3 日志切割之 Logrotate
Centos 中 rsyslog 负责写入日志,logrotate 负责备份和删除旧日志
> 定时任务
```
定时任务:/etc/cron.daily/logrotate
定时任务执行的程序:/usr/sbin/logrotate /etc/logrotate.conf
```
> 配置
```
/etc/logrotate.conf # 主配置文件
/etc/logrotate.d # 配置目录
```
备注:
```
当发现系统日志等没有轮转时,可以手动执行 "/usr/sbin/logrotate /etc/logrotate.conf" 查看下是否运行正常
当配置文件中有异常的配置时,logrotate 无法正常工作(一个异常配置会影响所有使用 logrotate 进行管理日志的服务)
```
> 常见 logrotate 异常
```
error: /etc/logrotate.conf:xx duplicate log entry for /var/log/xxx
/etc/logrotate.conf:xx 行有重复配置项,将重复项清理下即可
```
### 3.3.1 通用服务日志清理工具
如果是业务日志,也可以通过如下工具进行清理
如下工具会启动单独进程进行管理日志
[shell_logrotate](https://github.com/meetbill/linux_tools/tree/master/17_logrotate)
## 3.4 lsof
Lsof 是遵从 Unix 哲学的典范,它只做一件事情,并且做的相当完美——它可以列出某个进程打开的所有文件信息。打开的文件可能是普通的文件,目录,NFS 文件,块文件,字符文件,共享库,常规管道,明明管道,符号链接,Socket 流,网络 Socket,UNIX 域 Socket,以及其它更多。因为 Unix 系统中几乎所有东西都是文件,你可以想象 lsof 该有多有用。
### 3.4.1 如何使用 lsof?
> 列出所有打开的文件 不带任何参数运行 lsof 会列出所有进程打开的所有文件。
```
lsof
```
> 找出谁在使用某个文件
```
lsof /path/to/file
```
> 列出某个用户打开的所有文件
```
lsof -u meetbill
```
> 查找某个程序打开的所有文件 -c 选项限定只列出以 apache 开头的进程打开的文件:
```
lsof -c apache
```
> 列出所有由某个 PID 对应的进程打开的文件 -p 选项让你可以使用进程 id 来过滤输出。
```
lsof -p 1
```
> 列出所有网络连接 lsof 的 -i 选项可以列出所有打开了网络套接字(TCP 和 UDP)的进程。
```
lsof -i
```
> 列出所有 TCP 网络连接 也可以为 -i 选项加上参数,比如 tcp,tcp 选项会强制 lsof 只列出打开 TCP sockets 的进程。
```
lsof -i tcp
```
> 找到使用某个端口的进程
```
lsof -i :25
```
> 找到某个用户的所有网络连接 使用 -a 将 -u 和 -i 选项组合可以让 lsof 列出某个用户的所有网络行为。
```
lsof -a -u hacker -i
```
> 列出所有 NFS(网络文件系统)文件 这个参数很好记,-N 就对应 NFS。
```
lsof -N
```
> 列出所有 UNIX 域 Socket 文件 这个选项也很好记,-U 就对应 UNIX。
```
lsof -U
```
> 列出所有对应某个组 id 的进程
```
lsof -g 1234
```
> 列出所有与某个描述符关联的文件
```
lsof -d 2
```
## 3.4.2 lsof -p
> lsof -p 24406
```
COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME
python2.7 24406 wangbin34 cwd DIR 8,8 4096 53002355 /home/users/meetbill/dev/butterfly
python2.7 24406 wangbin34 rtd DIR 8,2 4096 2 /
python2.7 24406 wangbin34 txt REG 8,8 10265 52711469 /bin/python2.7
python2.7 24406 wangbin34 mem REG 0,0 0 [heap] (stat: No such file or directory)
python2.7 24406 wangbin34 mem REG 8,2 17624 246236 /lib64/libuuid.so.1.2
python2.7 24406 wangbin34 mem REG 8,2 105080 246045 /lib64/ld-2.3.4.so
python2.7 24406 wangbin34 mem REG 8,2 1493186 246040 /lib64/tls/libc-2.3.4.so
python2.7 24406 wangbin34 mem REG 8,2 17943 246057 /lib64/libdl-2.3.4.so
python2.7 24406 wangbin34 mem REG 8,2 613297 246035 /lib64/tls/libm-2.3.4.so
python2.7 24406 wangbin34 mem REG 8,2 106203 246042 /lib64/tls/libpthread-2.3.4.so
python2.7 24406 wangbin34 mem REG 8,2 17367 246060 /lib64/libutil-2.3.4.so
...
python2.7 24406 wangbin34 0r CHR 1,3 4869 /dev/null
python2.7 24406 wangbin34 1w REG 8,8 319 52982340 /home/users/meetbill/dev/butterfly/__stdout
python2.7 24406 wangbin34 2w REG 8,8 319 52982340 /home/users/meetbill/dev/butterfly/__stdout
python2.7 24406 wangbin34 3u IPv4 3659770919 TCP :8021 (LISTEN)
python2.7 24406 wangbin34 4w REG 8,8 48845 53739748 /home/users/meetbill/dev/butterfly/logs/info.log
python2.7 24406 wangbin34 5w REG 8,8 205892 53739816 /home/users/meetbill/dev/butterfly/logs/common.log
python2.7 24406 wangbin34 6w REG 8,8 240 53739811 /home/users/meetbill/dev/butterfly/logs/common.log.wf
python2.7 24406 wangbin34 7r CHR 1,9 4439 /dev/urandom
python2.7 24406 wangbin34 9w REG 8,8 10005 53739790 /home/users/meetbill/dev/butterfly/logs/acc.log
```
> * COMMAND:进程的名称
> * PID:进程标识符
> * USER:进程所有者
> * FD:文件描述符,应用程序通过文件描述符识别该文件。如 cwd、txt 等
> * cwd 值表示应用程序的当前工作目录
> * TYPE:文件类型,如 DIR、REG 等
> * 文件和目录分别称为 REG 和 DIR
> * CHR 表示字符;(fopen,打开文件)
> * BLK 表示块设备;
> * UNIX、FIFO 和 IPv4,分别表示 UNIX 域套接字、先进先出 (FIFO) 队列和网际协议 (IP) 套接字。
> * DEVICE:指定磁盘的名称
> * SIZE:文件的大小
> * NODE:索引节点(文件在磁盘上的标识)
> * NAME:打开文件的确切名称
# 4 网络相关
## 4.1 curl
### 4.1.1 HTTP 请求
GET 和 POST 是 HTTP 请求的两种基本方法,最直观的区别就是 GET 把参数包含在 URL 中,POST 通过 request body 传递参数。
```
在万维网世界,TCP 就像汽车,我们用 TCP 来运输数据,它很可靠,
从来不会发生丢件少件的现象。但是如果路上跑的全是看起来一模一样
的汽车,那这个世界看起来是一团混乱,送急件的汽车可能被前面满载
货物的汽车拦堵在路上,整个交通系统一定会瘫痪。为了避免这种情况
发生,交通规则 HTTP 诞生了。HTTP 给汽车运输设定了好几个服务类别,
有 GET, POST, PUT, DELETE 等等,HTTP 规定,当执行 GET 请求的时候,
要给汽车贴上 GET 的标签(设置 method 为 GET),而且要求把传送的数
据放在车顶上 (url) 以方便记录。如果是 POST 请求,就要在车上贴上
POST 的标签,并把货物放在车厢里。
当然,你也可以在 GET 的时候往车厢内偷偷藏点货物,但是这是很不
光彩;也可以在 POST 的时候在车顶上也放一些数据。
```
### 4.1.2 curl 基础
在介绍前,我需要先做两点说明:
1. 下面的例子中会使用 [httpbin.org](http://httpbin.org/) ,httpbin 提供客户端测试 http 请求的服务,非常好用,具体可以查看他的网站。
2. 大部分没有使用缩写形式的参数,例如我使用 `--request` 而不是 `-X` ,这是为了好记忆。
下面开始简单介绍几个命令:
**get**
> * curl protocol://address:port/url?args
直接以个 GET 方式请求一个 url,输出返回内容:
``` sh
curl httpbin.org
```
返回
``` html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='content-type' value='text/html;charset=utf8'>
<meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>
<title>httpbin(1): HTTP Client Testing Service</title>
<style type='text/css' media='all'>
/* style: man */
body#manpage {margin:0}
.mp {max-width:100ex;padding:0 9ex 1ex 4ex}
.mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
.mp h2 {margin:10px 0 0 0}
......
```
**post**
> * curl --data "args" "protocol://address:port/url"
> * -d/--data <data> HTTP POST 方式传送数据
> * --data-ascii <data> 以 ascii 的方式 post 数据
> * --data-binary <data> 以二进制的方式 post 数据
使用 `--request` 指定请求类型, `--data` 指定数据,例如:
``` sh
curl httpbin.org/post --request POST --data "name=keenwon&website=http://keenwon.com"
```
返回:
``` html
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "tomshine",
"website": "http://tomshine.xyz"
},
"headers": {
"Accept": "*/*",
"Content-Length": "41",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "curl/7.43.0"
},
"json": null,
"origin": "121.35.209.62",
"url": "http://httpbin.org/post"
}
```
这个返回值是 httpbin 输出的,可以清晰的看出我们发送了什么数据,非常实用。
**form 表单提交**
form 表单提交使用 `--form`,使用 `@` 指定本地文件,例如我们提交一个表单,有字段 name 和文件 f:
``` sh
curl httpbin.org/post --form "name=tomshine" --form "f=@/Users/tomshine/test.txt"
```
返回:
``` json
{
"args": {},
"data": "",
"files": {
"f": "Hello Curl!\n"
},
"form": {
"name": "tomshine"
},
"headers": {
"Accept": "*/*",
"Content-Length": "296",
"Content-Type": "multipart/form-data; boundary=------------------------3bd3dc24dca6daf2",
"Host": "httpbin.org",
"User-Agent": "curl/7.43.0"
},
"json": null,
"origin": "112.95.153.98",
"url": "http://httpbin.org/post"
}
```
### 4.1.3 curl 深入
**显示头信息**
使用 `--include` 在输出中包含头信息,使用 `--head` 只返回头信息,例如:
``` sh
curl httpbin.org/post --include --request POST --data "name=tomshine"
```
返回:
``` html
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 18 Sep 2016 01:23:28 GMT
Content-Type: application/json
Content-Length: 363
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "tomshine"
},
"headers": {
"Accept": "*/*",
"Content-Length": "13",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "curl/7.43.0"
},
"json": null,
"origin": "121.35.209.62",
"url": "http://httpbin.org/post"
}
```
再例如,只显示头信息的话:
``` sh
curl httpbin.org --head
```
返回:
``` html
HTTP/1.1 200 OK
Server: nginx
Date: Sun, 18 Sep 2016 01:24:29 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 12150
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
```
**详细显示通信过程**
使用 `--verbose` 显示通信过程,例如:
``` sh
curl httpbin.org/post --verbose --request POST --data "name=tomshine"
```
返回:
``` html
* Trying 54.175.219.8...
* Connected to httpbin.org (54.175.219.8) port 80 (#0)
> POST /post HTTP/1.1
> Host: httpbin.org
> User-Agent: curl/7.43.0
> Accept: */*
> Content-Length: 13
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 13 out of 13 bytes
< HTTP/1.1 200 OK
< Server: nginx
< Date: Sun, 18 Sep 2016 01:25:03 GMT
< Content-Type: application/json
< Content-Length: 363
< Connection: keep-alive
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Credentials: true
<
{
"args": {},
"data": "",
"files": {},
"form": {
"name": "tomshine"
},
"headers": {
"Accept": "*/*",
"Content-Length": "13",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "curl/7.43.0"
},
"json": null,
"origin": "121.35.209.62",
"url": "http://httpbin.org/post"
}
* Connection #0 to host httpbin.org left intact
```
**设置头信息**
使用 `--header` 设置头信息,`httpbin.org/headers` 会显示请求的头信息,我们测试下:
``` sh
curl httpbin.org/headers --header "a:1"
```
返回:
``` html
{
"headers": {
"A": "1",
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.43.0"
}
}
```
同样的,可以使用 `--header` 设置 `User-Agent` 等。
**Referer 字段**
设置 `Referer` 字段很简单,使用 `--referer` ,例如:
``` sh
curl httpbin.org/headers --referer http://tomshine.xyz
```
返回:
``` html
{
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"Referer": "http://tomshine.xyz",
"User-Agent": "curl/7.43.0"
}
}
```
**包含 cookie**
使用 `--cookie` 来设置请求的 cookie,例如:
``` sh
curl httpbin.org/headers --cookie "name=tomshine;website=http://tomshine.xyz"
```
返回:
``` html
{
"headers": {
"Accept": "*/*",
"Cookie": "name=tomshine;website=http://tomshine.xyz",
"Host": "httpbin.org",
"User-Agent": "curl/7.43.0"
}
}
```
**自动跳转**
使用 `--location` 参数会跟随链接的跳转,例如:
``` sh
curl httpbin.org/redirect/1 --location
```
httpbin.org/redirect/1 会 302 跳转,所以返回:
``` html
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.43.0"
},
"origin": "121.35.209.62",
"url": "http://httpbin.org/get"
}
```
**http 认证**
当页面需要认证时,可以使用 `--user` ,例如:
``` sh
curl httpbin.org/basic-auth/tomshine/123456 --user tomshine:123456
```
返回:
``` html
{
"authenticated": true,
"user": "tomshine"
}
```
## 4.2 tcpdump
### 4.2.1 tcp 三次握手和四次挥手
**三次握手**
A 主动打开连接,B 被动打开连接
```
(1) 第一次握手:建立连接时,客户端 A 发送 SYN 包 (SYN=x) 到服务器 B,并进入 SYN_SEND 状态,等待服务器 B 确认。
(2) 第二次握手:服务器 B 收到 SYN 包,必须确认客户 A 的 SYN(ACK=x+1),同时自己也发送一个 SYN 包 (SYN=y),即 SYN+ACK 包,此时服务器 B 进入 SYN_RECV 状态。
(3) 第三次握手:客户端 A 收到服务器 B 的 SYN+ACK 包,向服务器 B 发送确认包 ACK(ACK=y+1),此包发送完毕,客户端 A 和服务器 B 进入 ESTABLISHED 状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据。
```
为什么 A 还要发送一次确认呢?
主要是为了防止已失效的连接请求报文段突然有传送到 B,因而产生错误。
***正常情况***
A 发出连接请求,但是因为连接请求报文丢失为未收到确认。于是 A 在重传一次连接请求,后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。A 共发送两个连接请求报文段,其中第一个丢失第二个到达了 B。
***异常情况***
A 发出的第一个连接请求报文段并没有丢失,而是在某些网络节点长时间滞留,导致延误到连接释放之后才到达了 B,本来这是一个早已经失效的报文段,但是 B 收到此失效的连接请求报文段之后,误以为是 A 又发出一次新的连接请求,于是就向 A 发送确认报段,同意建立连接。假如不采用三次握手,那么只要 B 发出确认,新的连接就建立了。由于现在 A 并没有发出建立连接的请求,因此不会理睬 B 的确认,也不会向 B 发送数据,但 B 却以为新的运输连接已经建立了,并且一直等待 A 发来数据。B 的资源就这样白白的浪费了,
采用三次握手的办法可以防止上述现象的发生。例如刚才的情况,A 不会向 B 的确认发出确认。B 由于接收不到确认,就知道 A 并没有要求建立连接。
使用 tcpdump 来验证 TCP 的三次握手
使用 ssh localhost 来连接主机,使用使用 tcpdump 来验证 TCP 的三次握手
[root@localhost apue]# tcpdump -i lo tcp -S
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes
1 15:08:03.039511 IP6 localhost.44910 > localhost.ssh: Flags [S], seq 3120401438, win 32752, options [mss 16376,sackOK,TS val 1319756 ecr 0,nop,wscale 7], length 0
2 15:08:03.039546 IP6 localhost.ssh > localhost.44910: Flags [S.], seq 404185237, ack 3120401439, win 32728, options [mss 16376,sackOK,TS val 1319756 ecr 1319756,nop,wscale 7], length 0
3 15:08:03.039576 IP6 localhost.44910 > localhost.ssh: Flags [.], ack 404185238, win 256, options [nop,nop,TS val 1319756 ecr 1319756], length 0
4 15:08:03.064809 IP6 localhost.ssh > localhost.44910: Flags [P.], seq 404185238:404185259, ack 3120401439, win 256, options [nop,nop,TS val 1319781 ecr 1319756], length 21
15:08:03.064944 IP6 localhost.44910 > localhost.ssh: Flags [.], ack 404185259, win 256, options [nop,nop,TS val 1319781 ecr 1319781], length 0
第一行就是第一次握手,客户端向服务器发送 SYN 标志 (Flags [S]),其中 seq = 3120401438;
第二行就是第二次握手,服务器向客户端进行 SYN+ACK 响应 (Flags [S.]), 其中 seq 404185237, ack 3120401439(是客户端的 seq+1 的值)
第三行就是第三次握手,客户端向服务器进行 ACK 响应 (Flags [.]), 其中 ack 404185238(就是服务器的 seq+1 的值)。
**四次挥手**
通信传输结束后,通信的双方都可以释放连接,现在 A 和 B 都处于 ESTABLISHED 状态。
由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动,一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) 客户端 A 发送一个 FIN 包,用来关闭客户 A 到服务器 B 的数据传送。序列号 seq = u,它等于前面已传送过的数据的最后一个字节的序号加 1. 这时 A 进入 FIN-WAIT-1(终止等待 1) 状态,等待 B 的确认。
(2) 服务器 B 收到这个 FIN,它发回一个 ACK,确认序号是 ack = u +1。和 SYN 一样,一个 FIN 将占用一个序号。这个报文段自己的序号是 v,等于 B 前面已经传送过的数据的最后一个字节的序号加 1。然后 B 进程进入 CLOSE-WAIT(关闭等待)状态。TCP 服务器进程这时应通知高层应用进程,因而从 A 到 B 的这个方向的连接就释放了,这时的 TCP 连接处于半关闭 (half-close) 状态, 即 A 已经没有数据要发送了,但是 B 若发送数据,A 仍要接收,也就是说从 B 到 A 这个方向的连接并没有关闭,这个状态可能会持续一些时间。A 收到来到 B 的确认后就进入 FIN-WAIT-2(终止等待 2) 状态,等待 B 发出的连接释放报文段。
(3) 若 B 已经没有要向 A 发送的数据,其应用进程就会通知 TCP 释放连接,这时 B 发出的连接释放报文段必须使 FIN = 1。现假定 B 的序号为 w(在半关闭状态 B 可能要发送一些数据)。B 还必须重复上次发送过的确认号 ack = u +1。这时 B 就进入了 LAST-ACK(最后确认)状态,等待 A 的确认。
(4)A 在收到 B 的连接释放报文段后,必须对此发出确认。在确认报文段中把 ACK 置 1,确认号 ack = w + 1,而自己的序号是 seq = u + 1(根据 TCP 标准,前面发送过的 FIN 报文段要消耗一个序号),然后进入到 TIME-WAIT(时间等待)状态。
***此时的 TCP 还没有完全的释放掉。必须经过时间等待计时器 (TIME-WAIT timer) 设置的时间 2MSL 后,A 才进入到 CLOSED 状态。***
> MSL 叫做最长报文段寿命,它是任何报文段被丢弃前在网络内的最长时间。
2MSL 也就是这个时间的两倍,RFC 建议设置为 2 分钟,但是 2 分钟可能太长了,因此 TCP 允许不同的实现使用更小的 MSL 值。
因此从 A 进入到 TIME-WAIT 状态后,要经过 4 分钟才能进入 CLOSED 状态,此案开始建立下一个新的连接。当 A 撤销相应的传输控制块 TCP 后,就结束了 TCP 连接。
使用 tcpdump 来验证 TCP 的四次挥手
退出 ssh 连接的主机,使用使用 tcpdump 来验证 TCP 的四次挥手
15:14:58.836149 IP6 localhost.44911 > localhost.ssh: Flags [P.], seq 1823848744:1823848808, ack 3857143125, win 305, options [nop,nop,TS val 1735551 ecr 1735551], length 64
15:14:58.836201 IP6 localhost.44911 > localhost.ssh: Flags [F.], seq 1823848808, ack 3857143125, win 305, options [nop,nop,TS val 1735551 ecr 1735551], length 0
15:14:58.837850 IP6 localhost.ssh > localhost.44911: Flags [.], ack 1823848809, win 318, options [nop,nop,TS val 1735554 ecr 1735551], length 0
15:14:58.842130 IP6 localhost.ssh > localhost.44911: Flags [F.], seq 3857143125, ack 1823848809, win 318, options [nop,nop,TS val 1735559 ecr 1735551], length 0
15:14:58.842150 IP6 localhost.44911 > localhost.ssh: Flags [.], ack 3857143126, win 305, options [nop,nop,TS val 1735559 ecr 1735559], length 0
***seq start:end 意思是初始序列号:结束序列号,end = start + length, 但是接受包的结束序号应该是 end - 1。***
比如 start = 1,length = 3,那么真正的结束序号是 1+3-1 = 3, 因为开始序号也算在内的!
seq 1823848744:1823848808 意思是初始序列号:结束序列号,其实后面那个就是前面那个加上包长度 length = 64,实际上结束序列号是没有使用的,真正使用的序号是开始序号到结束序号 -1,也就是 1823848807
第一次挥手中客户端发送 FIN 即 [F.] seq u = 1823848808,也就是上次发送的数据的最后一个字节的序号加 1
第二次挥手中服务器发送 ACK 即 [.] ack 1823848809 = u + 1
第三次挥手中服务器发送 FIN 即 [F.] seq v = 3857143125, ack 1823848809 = u + 1
第四次挥手中客户端发送 ACK 即 [.] ack 3857143126 = v + 1
1. 默认情况下(不改变 socket 选项),当你调用 close 函数时,如果发送缓冲中还有数据,TCP 会继续把数据发送完。
2. 发送了`FIN 只是表示这端不能继续发送数据(应用层不能再调用 send 发送)`,但是还可以接收数据。
3. 应用层如何知道对方关闭?
在最简单的阻塞模型中,当调用 recv 时,如果返回 0,则表示对方关闭,
在这个时候通常的做法就是也调用 close,那么 TCP 层就发送 FIN,继续完成四次握手;
***如果不调用 close,那么对方就会处于 FIN_WAIT_2 状态,而本端则会处于 CLOSE_WAIT 状态;***
4. 在很多时候,TCP 连接的断开都会由 TCP 层自动进行,例如你 CTRL+C 终止你的程序,TCP 连接依然会正常关闭。
### 4.2.2 tcpdump 使用
**针对特定网口抓包 (-i 选项)**
> tcpdump -i eth0
**指定抓包端口**
> tcpdump -i eth0 port 22
**抓取特定目标 ip 和端口的包**
> tcpdump -i eth0 dst 10.70.121.92 and port 22
**增加抓包时间戳 (-tttt 选项)**
> tcpdump -n -tttt -i eth0
## 4.3 nc
nc 检测端口更方便,同时批量进行检测端口的话是非常好的工具
### 4.3.1 语法
nc [-hlnruz][-g《网关...>][-G《指向器数目》][-i《延迟秒数》][-o《输出文件》][-p《通信端口》][-s《来源位址》][-v...][-w《超时秒数》][主机名称]『通信端口...]
```
参数说明:
-g《网关》 设置路由器跃程通信网关,最丢哦可设置 8 个。
-G《指向器数目》 设置来源路由指向器,其数值为 4 的倍数。
-h 在线帮助。
-i《延迟秒数》 设置时间间隔,以便传送信息及扫描通信端口。
-l 使用监听模式,管控传入的资料。
-n 直接使用 IP 地址,而不通过域名服务器。
-o《输出文件》 指定文件名称,把往来传输的数据以 16 进制字码倾倒成该文件保存。
-p《通信端口》 设置本地主机使用的通信端口。
-r 乱数指定本地与远端主机的通信端口。
-s《来源位址》 设置本地主机送出数据包的 IP 地址。
-u 使用 UDP 传输协议。
-v 显示指令执行过程。
-w《超时秒数》 设置等待连线的时间。
-z 使用 0 输入 / 输出模式,只在扫描通信端口时使用。
```
### 4.3.2 TCP 端口扫描
```
# nc -v -z -w2 10.20.144.145 1-100
Connection to 10.20.144.145 22 port [tcp/ssh] succeeded!
nc: connect to 10.20.144.145 port 23 (tcp) failed: Connection refused
nc: connect to 10.20.144.145 port 24 (tcp) failed: Connection refused
nc: connect to 10.20.144.145 port 25 (tcp) failed: Connection refused
...
Connection to 10.20.144.145 80 port [tcp/http] succeeded!
...
扫描 10.20.144.145 的端口 范围是 1-100
```
不加 -v 时仅输出 succeeded 的结果
### 4.3.3 扫描 UDP 端口
```
# nc -u -z -w2 10.20.144.145 1-1000 // 扫描 10.20.144.145 的端口 范围是 1-1000
扫描指定端口
```
# 5 日志相关操作
## 5.1 截取某段时间内的日志
```
sed -n '/2018-03-06 15:25:00/,/2018-03-06 15:30:00/p' access.log >25-30.log
```
## 5.2 处理日志文件中上下关联的两行
```
awk 'pattern { action };pattern { action };'
```
凡是被 {} 包裹的,就是 action, 凡是没有被{}包裹的,就是 pattern,
文件 d.txt 如下内容
```
ggg 1
portals: 192.168.5.41:3260
werew 2
portals: 192.168.5.43:3260
```
如何把文件 d.txt 内容变为如下内容
```
ggg 192.168.5.41:3260
werew 192.168.5.43:3260
```
方法
```
awk '/port/{print a" "$2}{a=$1}' d.txt
```
处理第一行的时候,以 port 开头吗?很明显,不以 port 开头,所以那个 pattern 不匹配,action 不执行。但执行了后面的 a=$1
处理第二行的时候,以 port 开头,打印出来 a 和本行 $2,再处理就是个循环过程。
总之,编写模式匹配时候,匹配的模式为第二行中的内容
# 6 应用服务相关
## 6.1 排查 java CPU 性能问题
[show-busy-java-threads.sh](https://github.com/meetbill/op_practice_code/blob/master/Linux/op/show-busy-java-threads.sh)
```
curl -o show-busy-Java-threads.sh https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/op/show-busy-java-threads.sh
```
用于快速排查`Java`的`CPU`性能问题 (`top us`值过高),自动查出运行的`Java`进程中消耗`CPU`多的线程,并打印出其线程栈,从而确定导致性能问题的方法调用。
PS,如何操作可以参见 [@bluedavy](http://weibo.com/bluedavy) 的《分布式 Java 应用》的【5.1.1 cpu 消耗分析】一节,说得很详细:
1. `top`命令找出有问题`Java`进程及线程`id`:
1. 开启线程显示模式
1. 按`CPU`使用率排序
1. 记下`Java`进程`id`及其`CPU`高的线程`id`
1. 用进程`id`作为参数,`jstack`有问题的`Java`进程
1. 手动转换线程`id`成十六进制(可以用`printf %x 1234`)
1. 查找十六进制的线程`id`(可以用`grep`)
1. 查看对应的线程栈
查问题时,会要多次这样操作以确定问题,上面过程**太繁琐太慢了**。
### 6.1.1 用法
```bash
show-busy-java-threads.sh
# 从 所有的 Java 进程中找出最消耗 CPU 的线程(缺省 5 个),打印出其线程栈。
show-busy-java-threads.sh -c 《要显示的线程栈数》
show-busy-java-threads.sh -c 《要显示的线程栈数》 -p 《指定的 Java Process>
##############################
# 注意:
##############################
# 如果 Java 进程的用户 与 执行脚本的当前用户 不同,则 jstack 不了这个 Java 进程。
# 为了能切换到 Java 进程的用户,需要加 sudo 来执行,即可以解决:
sudo show-busy-java-threads.sh
```
### 6.1.2 示例
```bash
$ show-busy-java-threads.sh
[1] Busy(57.0%) thread(23355/0x5b3b) stack of java process(23269) under user(admin):
"pool-1-thread-1" prio=10 tid=0x000000005b5c5000 nid=0x5b3b runnable [0x000000004062c000]
java.lang.Thread.State: RUNNABLE
at java.text.DateFormat.format(DateFormat.java:316)
at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)
at com.xxx.foo.shared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:127)
at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
[2] Busy(26.1%) thread(24018/0x5dd2) stack of java process(23269) under user(admin):
"pool-1-thread-2" prio=10 tid=0x000000005a968800 nid=0x5dd2 runnable [0x00000000420e9000]
java.lang.Thread.State: RUNNABLE
at java.util.Arrays.copyOf(Arrays.java:2882)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
at java.lang.Ab
gitextract_bpfyvm88/ ├── README.md ├── SUMMARY.md ├── doc/ │ ├── HA/ │ │ ├── README.md │ │ ├── keepalived.md │ │ ├── lb.md │ │ └── lvs.md │ ├── Linux/ │ │ ├── README.md │ │ ├── base.md │ │ ├── op.md │ │ ├── optimize.md │ │ ├── safety.md │ │ ├── service.md │ │ ├── shell.md │ │ └── tools.md │ ├── README.md │ ├── cloud/ │ │ ├── README.md │ │ ├── aliyun.md │ │ ├── aws.md │ │ ├── docker.md │ │ ├── k8s.md │ │ ├── kvm.md │ │ ├── openstack.md │ │ └── physical_machine.md │ ├── cluster/ │ │ └── zookeeper.md │ ├── db/ │ │ ├── README.md │ │ ├── memcache.md │ │ ├── mongodb.md │ │ ├── mysql.md │ │ └── redis.md │ ├── monitor/ │ │ ├── README.md │ │ ├── monit.md │ │ └── zabbix.md │ ├── other/ │ │ ├── README.md │ │ └── windows.md │ ├── store/ │ │ ├── RAID.md │ │ ├── README.md │ │ ├── ceph.md │ │ ├── gfs.md │ │ ├── glusterfs.md │ │ ├── moosefs.md │ │ └── store.md │ └── web/ │ ├── README.md │ ├── butterfly.md │ ├── django.md │ ├── nginx.md │ └── web_base.md ├── run_web.sh └── standard.md
Condensed preview — 48 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (778K chars).
[
{
"path": "README.md",
"chars": 1774,
"preview": "# 运维实践指南\n\n[](h"
},
{
"path": "SUMMARY.md",
"chars": 1541,
"preview": "# Summary\n\n* [前言](doc/README.md)\n* [Linux 篇](doc/Linux/README.md)\n * [Linux 基础](doc/Linux/base.md)\n * [Linux 工具](d"
},
{
"path": "doc/HA/README.md",
"chars": 47,
"preview": "# 集群应用篇\n\n> * 负载均衡\n> * LVS\n> * 高可用的 LVS 负载均衡集群\n"
},
{
"path": "doc/HA/keepalived.md",
"chars": 15504,
"preview": "## Keepalived 使用\n\n<!-- vim-markdown-toc GFM -->\n* [1 Keepalived 介绍及安装](#1-keepalived-介绍及安装)\n * [1.1 介绍](#11-介绍)\n "
},
{
"path": "doc/HA/lb.md",
"chars": 2203,
"preview": "## 负载均衡\n\n<!-- vim-markdown-toc GFM -->\n* [解决的问题](#解决的问题)\n* [网络层次上的负载均衡](#网络层次上的负载均衡)\n * [四层负载均衡](#四层负载均衡)\n * [七层负"
},
{
"path": "doc/HA/lvs.md",
"chars": 6349,
"preview": "## LVS\n\n<!-- vim-markdown-toc GFM -->\n* [LVS 简介](#lvs-简介)\n* [数据包三种转发方式](#数据包三种转发方式)\n * [NAT 网络地址翻译技术](#nat-网络地址翻译技术)"
},
{
"path": "doc/Linux/README.md",
"chars": 102,
"preview": "# Linux 篇\n\n> * Linux 基础\n> * Linux 工具\n> * Linux 安全\n> * Linux 优化\n> * 脚本编程 (shell)\n> * 常见服务架设\n> * 常用问题处理\n"
},
{
"path": "doc/Linux/base.md",
"chars": 45306,
"preview": "## Linux 基础\n\n<!-- vim-markdown-toc GFM -->\n\n* [安装](#安装)\n * [安装准备](#安装准备)\n * [安装 CentOS6.8](#安装-centos68)\n * [系统"
},
{
"path": "doc/Linux/op.md",
"chars": 2795,
"preview": "# 常用问题处理\n\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 系统配置](#1-系统配置)\n * [1.1 Yum 安装安装包时提示证书过期](#11-yum-安装安装包时提示证书过期)\n * ["
},
{
"path": "doc/Linux/optimize.md",
"chars": 24147,
"preview": "# Linux 优化\n\n<!-- vim-markdown-toc GFM -->\n\n* [说明](#说明)\n * [应用类型](#应用类型)\n * [监测工具](#监测工具)\n * [综合工具之 sar](#综合"
},
{
"path": "doc/Linux/safety.md",
"chars": 15874,
"preview": "## Linux 安全\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 禁止 ping](#1-禁止-ping)\n* [2 禁止密码登陆](#2-禁止密码登陆)\n* [3 ssh 防暴力破解及提高 ssh 安全]("
},
{
"path": "doc/Linux/service.md",
"chars": 15364,
"preview": "# 常见服务架设\n\n<!-- vim-markdown-toc GFM -->\n\n* [NTP](#ntp)\n * [简介](#简介)\n * [ntpd](#ntpd)\n * [NTP Server 安装配置](#"
},
{
"path": "doc/Linux/shell.md",
"chars": 29157,
"preview": "# Shell 基础及实例\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 shell 编程环境](#1-shell-编程环境)\n * [1.1 编程基础知识](#11-编程基础知识)\n * ["
},
{
"path": "doc/Linux/tools.md",
"chars": 40153,
"preview": "# Linux 工具篇\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 编程相关](#1-编程相关)\n * [1.1 vim IDE 工具](#11-vim-ide-工具)\n * [1.2 Git 分布"
},
{
"path": "doc/README.md",
"chars": 138,
"preview": "# 运维实践指南\n\n此笔记中记录着学习点滴,希望可以帮到更多的人。\n\n如果这其中有些是你正困惑的地方,那么此笔记也许能帮到你,如果有好的建议,[戳这](https://github.com/meetbill/op_practice_book"
},
{
"path": "doc/cloud/README.md",
"chars": 81,
"preview": "# 物理机,云服务及虚拟化篇\n\n> * 物理机\n> * AWS\n> * 阿里云\n> * KVM\n> * Docker\n> * OpenStack\n> * K8s\n"
},
{
"path": "doc/cloud/aliyun.md",
"chars": 5623,
"preview": "# 阿里云\n\n<!-- vim-markdown-toc GFM -->\n* [1 访问控制 (RAM)](#1-访问控制-ram)\n * [1.1 创建 ECS 管理员](#11-创建-ecs-管理员)\n* [2 ECS](#2-e"
},
{
"path": "doc/cloud/aws.md",
"chars": 12974,
"preview": "## AWS\n<!-- vim-markdown-toc GFM -->\n* [0 AWS 产品](#0-aws-产品)\n* [1 使用 AWS S3](#1-使用-aws-s3)\n * [基本概念](#基本概念)\n * [基本"
},
{
"path": "doc/cloud/docker.md",
"chars": 21791,
"preview": "# Docker\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 CentOS7 安装 Docker](#1-centos7-安装-docker)\n * [1.1 准备](#11-准备)\n * [1.2"
},
{
"path": "doc/cloud/k8s.md",
"chars": 9778,
"preview": "## K8s\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 Kubernetes 概述](#1-kubernetes-概述)\n * [1.1 简介](#11-简介)\n * [1.2 特性](#12-特"
},
{
"path": "doc/cloud/kvm.md",
"chars": 10925,
"preview": "\n<!-- vim-markdown-toc GFM -->\n* [1 什么是 KVM](#1-什么是-kvm)\n* [2 安装 KVM](#2-安装-kvm)\n * [2.1 系统要求](#21-系统要求)\n * [2.2 安"
},
{
"path": "doc/cloud/openstack.md",
"chars": 80,
"preview": "# OpenStack\n\n[OpenStack 实践](https://github.com/meetbill/openstack_install/wiki)\n"
},
{
"path": "doc/cloud/physical_machine.md",
"chars": 648,
"preview": "## 物理机常见问题即处理方法\n\n<!-- vim-markdown-toc GFM -->\n* [开机无法启动](#开机无法启动)\n * [提示无法找到系统盘](#提示无法找到系统盘)\n * [场景](#场景)\n "
},
{
"path": "doc/cluster/zookeeper.md",
"chars": 3365,
"preview": "## ZooKeeper\n<!-- vim-markdown-toc GFM -->\n\n* [1 ZooKeeper 安装](#1-zookeeper-安装)\n * [2.1 单机模式](#21-单机模式)\n * [2.2 集群"
},
{
"path": "doc/db/README.md",
"chars": 42,
"preview": "# 数据库篇\n\n> * MySQL\n> * MongoDB\n> * Redis\n\n"
},
{
"path": "doc/db/memcache.md",
"chars": 2506,
"preview": "## MemCache\r\n\r\n<!-- vim-markdown-toc GFM -->\r\n\r\n* [1 Memcache 使用](#1-memcache-使用)\r\n * [1.1 telnet 访问](#11-telnet-访问)\r"
},
{
"path": "doc/db/mongodb.md",
"chars": 2134,
"preview": "## Mongodb\n<!-- vim-markdown-toc GFM -->\n\n* [1 安装](#1-安装)\n* [2 常见问题](#2-常见问题)\n * [(1) 启动异常](#1-启动异常)\n* [3 Mongodb 备份]"
},
{
"path": "doc/db/mysql.md",
"chars": 24765,
"preview": "# Mysql\n\n<!-- vim-markdown-toc GFM -->\n* [前言](#前言)\n * [MySQL 引擎](#mysql-引擎)\n * [查看下是否支持 InnoDB](#查看下是否支持-innodb)\n*"
},
{
"path": "doc/db/redis.md",
"chars": 81452,
"preview": "<!-- vim-markdown-toc GFM -->\n\n* [1 Redis](#1-redis)\n * [1.1 持久化](#11-持久化)\n * [1.1.1 AOF 重写机制](#111-aof-重写机制)\n"
},
{
"path": "doc/monitor/README.md",
"chars": 32,
"preview": "# 监控篇\n \n> * zabbix\n> * monit\n"
},
{
"path": "doc/monitor/monit.md",
"chars": 4827,
"preview": "## 目录\n\n<!-- vim-markdown-toc GFM -->\n* [概述](#概述)\n* [常用操作](#常用操作)\n * [支持命令行的选项](#支持命令行的选项)\n * [命令行参数](#命令行参数)\n* [配置"
},
{
"path": "doc/monitor/zabbix.md",
"chars": 443,
"preview": "# zabbix\n\n<!-- vim-markdown-toc GFM -->\n* [快速安装](#快速安装)\n* [zabbix 模板](#zabbix-模板)\n* [zabbix 管理工具](#zabbix-管理工具)\n\n<!-- vi"
},
{
"path": "doc/other/README.md",
"chars": 22,
"preview": "# 其他篇\n\n> * windows下服务\n"
},
{
"path": "doc/other/windows.md",
"chars": 524,
"preview": "# windows服务\n\n \n<!-- vim-markdown-toc GFM -->\n* [xp下建设VPN服务器](#xp下建设vpn服务器)\n\n<!-- vim-markdown-toc -->\n## xp下建设VPN服务器\n\n可以"
},
{
"path": "doc/store/RAID.md",
"chars": 15011,
"preview": "# 磁盘及 RAID\n<!-- vim-markdown-toc GFM -->\n* [磁盘管理](#磁盘管理)\n * [查看磁盘信息](#查看磁盘信息)\n * [lsscsi](#lsscsi)\n * ["
},
{
"path": "doc/store/README.md",
"chars": 79,
"preview": "# 存储篇\n\n> * 磁盘及 RAID\n> * DAS/SAN/NAS\n> * gfs\n> * Glusterfs\n> * ceph\n> * moosefs\n"
},
{
"path": "doc/store/ceph.md",
"chars": 5075,
"preview": "## Ceph\n\n<!-- vim-markdown-toc GFM -->\n* [Ceph](#ceph)\n * [Ceph 对象存储](#ceph-对象存储)\n * [Ceph 块设备](#ceph-块设备)\n * ["
},
{
"path": "doc/store/gfs.md",
"chars": 8668,
"preview": "# GFS\n\n<!-- vim-markdown-toc GFM -->\n * [分布式文件系统的要求](#分布式文件系统的要求)\n * [GFS 基于的假设](#gfs-基于的假设)\n* [架构](#架构)\n * [Ch"
},
{
"path": "doc/store/glusterfs.md",
"chars": 8177,
"preview": "# GlusterFS\n\n<!-- vim-markdown-toc GFM -->\n* [说明](#说明)\n * [简介](#简介)\n * [GlusterFS 在企业中的应用场景](#glusterfs-在企业中的应用场景)"
},
{
"path": "doc/store/moosefs.md",
"chars": 12300,
"preview": "## MooseFS\n\n<!-- vim-markdown-toc GFM -->\n* [MooseFS 简介](#moosefs-简介)\n * [功能特性](#功能特性)\n * [架构原理](#架构原理)\n * "
},
{
"path": "doc/store/store.md",
"chars": 11088,
"preview": "# DAS/SAN/NAS\n\n目前常见的三种存储结构\n\n> * DAS:直连存储\n> * SAN:存储区域网\n> * NAS:网络附属存储\n\n<!-- vim-markdown-toc GFM -->\n* [DAS](#das)\n* [SA"
},
{
"path": "doc/web/README.md",
"chars": 59,
"preview": "# web篇\n\n> * web 基础\n> * nginx\n> * django\n> * butterfly 【推荐】\n"
},
{
"path": "doc/web/butterfly.md",
"chars": 13780,
"preview": "# butterfly\n\n<div align=center><img src=\"https://github.com/meetbill/butterfly/blob/master/images/butterfly.png\" width=\""
},
{
"path": "doc/web/django.md",
"chars": 11315,
"preview": "# django\n\nenv\n\n```bash\n python 2.6.6\n django_1.4.22\n\n```\n\n##【目录】\n\n\n<!-- vim-markdown-toc GFM -->\n* [django 开始](#dj"
},
{
"path": "doc/web/nginx.md",
"chars": 27168,
"preview": "# nginx\n\n<!-- vim-markdown-toc GFM -->\n\n* [功能](#功能)\n* [安装](#安装)\n * [安装依赖](#安装依赖)\n * [下载](#下载)\n * [编译安装](#编译安装)\n"
},
{
"path": "doc/web/web_base.md",
"chars": 5702,
"preview": "## web 基础\n<!-- vim-markdown-toc GFM -->\n\n* [1 一次完整的 HTTP 请求所经历的 7 个步骤](#1-一次完整的-http-请求所经历的-7-个步骤)\n* [2 HTTP 报文](#2-http"
},
{
"path": "run_web.sh",
"chars": 1181,
"preview": "#########################################################################\n# File Name: run_web.sh\n# Author: Bill\n# mail:"
},
{
"path": "standard.md",
"chars": 3830,
"preview": "# 文档规范\n<!-- vim-markdown-toc GFM -->\n* [1 工具说明](#1-工具说明)\n* [2 文档章节的划分](#2-文档章节的划分)\n * [2.1 文章标题](#21-文章标题)\n * [2.2"
}
]
About this extraction
This page contains the full source code of the meetbill/op_practice_book GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 48 files (494.0 KB), approximately 222.3k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.