Systemd 工具集

除了整齐划一的 Unit 文件,Systemd 的魔力还体现在它自带的一套犹如百宝箱一样的工具集上。

命令 功能
systemctl 用于检查和控制各种系统服务和资源的状态
bootctl 用于查看和管理系统启动分区
hostnamectl 用于查看和修改系统的主机名和主机信息
journalctl 用于查看系统日志和各类应用服务日志
localectl 用于查看和管理系统的地区信息
loginctl 用于管理系统已登录用户和 Session 信息
machinectl 用于操作 Systemd 容器
timedatectl 用于查看和管理系统的时间和时区信息
systemd-analyze 显示此次系统启动时运行每个服务所消耗的时间,可以用于分析系统启动过程中的性能瓶颈
systemd-ask-password 辅助性工具,用幸好遮障用户的任意输入,然后返回实际输入的内容
systemd-cat 用于将其它命令的输出重定向到系统日志
systemd-cgls 递归地显示指定 CGroup 的继承链
systemd-cgtop 显示系统当前最耗资源的 CGroup 单元
systemd-escape 辅助性工具,用于去除指定字符串中不能作为 Unit 文件名的字符
systemd-hwdb Systemd 的内部工具,用于更新硬件数据库
systemd-delta 对比当前系统配置与默认系统配置的差异
systemd-detect-virt 显示主机的虚拟化类型
systemd-inhibit 用于强制延迟或禁止系统的关闭、睡眠和待机时间
systemd-machine-id-setup Systemd 的内部工具,用于给 Systemd 容器生成 ID
systemd-notify Systemd 的内部工具,用于通知服务的状态变化
systemd-nspawn 用于创建 Systemd 容器
systemd-path Systemd 的内部工具,用于显示系统上下文中的各种路径配置
systemd-run 用于将任意指定的命令包装成一个临时的后台服务运行
systemd-stdio-bridge Systemd 的内部工具 ,用于将程序的标准输入输出重定向到系统总线
systemd-tmpfiles Systemd 的内部工具,用于创建和管理临时文件
systemd-tty-ask-password-agent 用于响应后台服务进程发出的输入密码请求

主机名、时间、地区信息管理

hostnamectl 显示主机名

hostnamectl set-hostname xxx 设置主机名

–host 和 –machine 参数可以远程查看和修改另一个主机或容器。

timedatectl 和 localectl 作用类似

电源管理

systemctl poweroff | halt 关机

systemctl reboot 重启

systemctl suspend 待机

systemctl hibernate 休眠

启动时间和运行状态分析

systemd-analyze time 打印最近一次启动消耗的时间

systemd-analyze blame | head 按启动耗时长短排序

systemd-analyze plot 生成详细 SVG 图

systemd-analyze dump 打印当前所有 Unit 状态

systemd-analyze dot 生成系统中所有 Unit 的依赖关系图

systemd-anylyze set-log-level emerg | alert | crit | err | warning | notice | info | debug

systemd-analyze verify xxx 检测 Unit 文件格式正确性

辅助性命令工具

systemd-ask-password
1
2
3
4
$ PASSWORD=$(systemd-ask-password "Hint test here:")
Hint test here: ***
$ echo $(PASSWORD)
abc
systemd-run
1
$ sudo systemd-run --uid=root --gid=root du -sh /*

当服务正常结束后(返回值为 0)会自动从 Systemd 的记录中消失,但其留下的日志仍然可以通过 journalctl 查看到。反之,如果服务异常结束(返回值非 0),则这个服务的记录会继续留在 Systemd 中,可以通过 “systemctl list-units”命令查看到。

$ sudo systemd-run --on-active=30 --time-property=AccuracySec=100ms /bin/touch/tmp/time-up生成临时定时器。

此时系统创建了两个临时的 Unit 文件。需要注意一点,其中的服务 Unit 文件会在定时器时间到时,且响应的任务顺利完成后自动删除。然后,定时器的 Unit 文件并不会随着任务的完成而自动消失,需要手工执行“systemctl stop”命令来移除。

systemctl reset-failed命令可以用来清理失败的临时服务。

Systemd 的系统资源管理

对服务的管理知识 Systemd 系统管理功能的冰山一角。在 Systemd 的生态圈中,Unit 文件统一了过去各种不同的系统资源配置格式,例如服务的启/停、定时任务、设备自动挂载、网络配置、设备配置、虚拟内存配置等。

Systemd 的 Unit 文件

Systemd 通过不同的文件后缀名来区分这些配置文件,下表列举了 Systemd 所支持的 12 中 Unit 文件类型。

后缀名 作用
.automount 用于控制自动挂载文件系统。即当指定的目录被访问时,立即会被自动挂载
.deviece 对应 /dev 目录下的设备,主要用于定义设备之间的依赖关系
.mount 定义系统结构层次中的一个挂载点,可以替代过去的 /etc/fstab 配置文件
.path 用于监控指定目录或文件的变化,并触发其它 Unit 运行
.scope 这种 Unit 文件不是用户创建的,而是 Systemd 运行时自己产生的,描述一些系统服务的分组信息
.service 封装守护进程的启动、停止、重启和重载操作,是最常见的一种 Unit 文件
.slice 用于表示一个 CGroup 的树,通常用户不会自己创建这样的 Unit 文件
.snapshot 用于表示一个由 systemctl snapshot 命令创建的 Systemd Units 运行状态快照
.socket 监控来自于系统或网络的数据消息,用于实现基于数据自动触发服务启动
.swap 定义一个用于做虚拟内存的交换分区
.target 用于对 Unit 进行逻辑分组,引导其它 Unit 的执行
.timer 用于配置在特定时间出发的任务,替代了 Crontab 的功能

Systemd 的 Unit 文件存放目录

路径 说明
/etc/systemd/system 系统或用户提供的配置文件
/run/systemd/system 软件运行时生成的配置文件
/usr/lib64/systemd/system 系统或第三方软件安装时添加的配置文件

CoreOS 系统提供的 Unit 文件大多放在 /usr/lib64/systemd/system 目录中,而/usr/lib64/systemd/system 这个目录在 CoreOS 中属于系统只堵分区。如果需要修改系统服务的管理配置,可以将这个目录中相应 Unit 文件复制到 /etc/systemd/system 中修改即可。

定时器

定时器的 Unit 文件

定时器的 Unit 文件除了通用的 Unit 段和 Install 段以外,还有一个特别的 Timer 段,这个区段的属性主要定义了定时器的触发时机和触发的任务。

  • OnActiveSec: 当“Timer 自己被启动后”的指定秒数时,触发定时器任务。
  • OnBootSec: 当“主机开始启动后”的指定秒数时,触发定时器任务。
  • OnstartupSec: 当“操作系统启动完成后”的指定秒数时,触发定时器任务。
  • OnUnitActiveSec: 当“定时器中的指定服务被启动后”的指定秒数时,触发定时器任务。
  • OnUnitInactiveSec: 当“定时器中的指定服务停止后”的指定秒数时,触发定时器任务。

可以同时使用多个触发时机。

以上这些属性的值都可以是一个数字(即秒数),或一个合法的字符串时间,可以用的单位如下表所示。

单位 意义
usec,us 微秒
msec,ms 毫秒
seconds,second,sec,s
minuters,minute,min,m 分钟
hours,hour,hr,h 小时
days,day,d
weeks,week,w
months,month
years,year,y
  • OnCalendar: 使用日历时间,在特定日期触发。常用的日期表示格式为 yyyy-MM-dd-hh:mm:ss,例如 2015-07-23 11:12:13。

  • Unit: 这个参数的值是一个服务 Unit 文件的名称。当定时器事件触发时,就会执行这个服务指定的指令。如果没有配置这个属性,默认会将与这个定时器同名(但后缀名是.service)的服务 Unit 文件作为被指定的任务。

  • AccuracySec: 这个值设定了触发事件的精度,默认为 1min。在精度范围内到期的定时器会在一个随机时间里被触发,防止在某个整点时刻出现大量并发的定时器事件。

  • Persistent: 值为 true 或 false。默认为 false,当设为 true 时,定时器启动时会检查从上次定时器结束到当前时刻是否有漏掉的 OnCalendar 触发事件,如果有则立即触发一次指定的任务;当设为 false 时,会忽略在定时器停止期间错过的 OnCalendar 事件。
  • WakeSystem: 值为 true 或 false。默认为 false,当设为 true 时,如果定时器触发时系统处于挂起(Suspend)状态,则会自动唤醒系统并执行任务。当设为 false 时,会忽略在系统挂起期间的事件。

systemd-tmpfiles-clean.timer

下面的 Unit 文件是 CoreOS 用于自动清理临时文件的定时器配置文件。

1
2
3
4
5
6
7
[Unit]
description=Daily Cleanup of Temporary Directories
Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8)
[Timer]
OnBootSec=15min
OnUnitActiceSec=1d

启动定时器

$ sudo systemctl start xxx.timer 启动定时器

$ sudo systemctl enable xxx.timer 注册为随系统启动

systemctl stopsystemctl disable 同理。

路径监控器

后缀名为“.path”的 Unit 文件是用来监控目录变化的。它的效果和定时器有些相似,也是在满足特定的条件时执行另一个作为指定任务的服务 Unit 文件。

路径监控器的 Unit 文件

路径监控器的 Unit 文件的特有配置段是 Path,用于配置监控的事件、目录和需要执行的任务。

  • PathExists: 列在这个属性后面的路径如果存在(被创建),就是触发任务。
  • PathExistsGlob: 功能和 PathExists 基本相同,但是这个属性后面的路径可以使用通配符。
  • PathChanged: 列在这个属性后面的文件和目录如果被修改或更新了,就会触发任务。
  • PathModified: 功能和 PathChanged 相似,不过前者只会在监听文件修改完成(文件句柄被释放)时触发任务,而这个属性后面的文件在每次修改被保存时都会触发任务。
  • DirectoryNotEmpty: 列在这个属性后面的目录如果不是空的,就是触发任务。

在同一个目录监控器中,可以同时使用多个监听条件,当任意一个触发时,都会执行指定的服务。

  • Unit: 这个参数的值是一个服务 Unit 文件的名称。当路径监控器的事件被触发时,就会执行这个服务指定的命令。如果没有配置这个属性,默认会将与这个监控器同名(但后缀名是.service)的服务 Unit 文件作为被指定的任务。

  • MakeDirectory: 值为 true 或 false。默认为 false,当设为 true 时,如果被监控的路径不存在,则会自动将其创建为一个目录(PathExists 事件监控的目录除外)。当设为 false 时,没有这个行为。

  • DirectoryMode: 这个属性用于指定自动创建的目录具有怎样的权限,默认值是 0755。

motdgen.path

1
2
3
4
5
[Unit]
Description=Watch for update engine configuration changes
[Path]
PathChanged=/etc/coreos/update.conf

数据监控器

后缀为“.socket”的 Unit 文件可以监控系统的指定 TCP/UDP 端口、系统消息队列、特殊设备、FIFO 管道或套接字文字的数据。

当检测到有数据来时,就启动指定服务。这样做的好处是,当操作系统启动时,许多对外提供服务的应用并不需要随系统立即开启,知道有真实的用户请求访问其监听的端口时,才会按需启动,从而节约了系统的开机时间。

数据监控器的 Unit 文件

  • ListenStream: 监听指定的 TCP 端口或 UNIX 套接字。(IP:PORT)
  • ListenDatagram: 监听指定的 UDP 端口。
  • ListenSequentialPacket: 监听指定的 Unix 套接字文件。
  • ListenFIFO: 监听指定的 FIFO(命名管道)文件。
  • ListenMessageQueue: 监听指定的 POSIX 消息队列。
  • ListenSpecial: 监听指定的字符设备文件或特殊设备文件。

在同一个数据监控器中,可以同时使用多个监听数据来源,当任意一个有数据到来时,都会执行指定的服务。

  • Service: 这个参数的值是一个服务 Unit 文件的名称。当监控到数据到达时,Systemd 就会启动这个服务并处理接收到的数据。如果没有配置这个属性,默认会将与这个监控器同名(但后缀名是.service)的服务 Unit 文件作为被指定的任务。

  • Accept: 值为 true 或 false。默认为 false,当设为 true 时,Systemd 会为每个连接请求创建一个新服务实例,通常只用于有连接的通信协议,列入监控 TCP 端口。当设为 false 时,Systemd会检查指定的服务是否已经在运行,如果没有运行,就会启动这个服务。

  • MaxConnections: 仅当“Accept=true”时有效,限制最大连接数,也就是最大的服务运行数量,超过这个数量的连接会被直接拒绝。
  • KeepAlive: 值为 true 或 false,只对 TCP 连接有效。默认为 false,当设为 true 时,建立的连接会被设置为长连接。
  • ExecStartPre 和 ExecStartPost: 在执行指定服务启动开始前和完成后需要执行的额外命令。
  • ExecStopPre 和 ExecStopPost: 在执行指定服务即将结束时和完整结束后需要执行的额外命令。
  • TimeoutSec: 指定 ExecStartPre、ExecStartPost、ExecStopPre 和 ExecStopPost 中每个命令的最长等待时间。
  • ReceiveBuffer 和 SendBuffer: 数据接收和发送缓存的大小。
  • RemoveOnStop: 值为 true 或 false,对系统消息队列、FIFO 管道或套接字文件有效。默认为 false,若设为 true,服务结束时会同时删除监控的文件。

sshd.socket

1
2
3
4
5
6
7
8
9
10
[Unit]
Description=OpenSSH Server Socket
Conflicts=sshd.service
[Socket]
ListenStream=22
Accept=yes
[Install]
WantedBy=sockets.target

挂载文件系统

后缀名为“.mount”的 Unit 文件用于记录和处理挂载文件系统。

挂载点的 Unit 文件

挂载点的 Unit 文件名必须以转义后的挂载目录命名。庄毅规则大致是将路径符号“/”替换为“-”,原本文件名中的“-”和其它非 ASCII 字符替换为“\x”开头的编号。

可以将挂载点路径去掉开头的分隔符作为“systemd-escape”命令的参数,从而获得转义的 Unix 文件名。

1
2
$ systemd-escape media/my-mount-point
media-my\x2dmount\x2dpoint
  • What: 被挂载设备的绝对路径。
  • Where: 挂载点目录的绝对路径,这个路径应该与该 Unit 文件名相对应。
  • Type: 挂载文件系统的类型。
  • Options: 挂载的参数,用逗号分隔。
  • SloppyOptions: 值为 true 或 false。默认为 false,若设为 true,当遇到无法识别的挂载参数时,会出错退出;若设为 false,则忽略无法识别的挂载参数。
  • DirectoryMode: 如果指定的挂载目录不存在时, Systemd 会将它自动创建出来。这个参数指定了目录的权限,默认为 0755。
  • TimeoutSec: 挂载超时的秒数。如果到了这个时间挂载仍然没有完成,则会判定为失败。

usr-share-oem.mount

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
DefaultDependencies=no
Wants=addon-run@usr-share-oem.service addon-config@usr-share-oem.service
Conflicts=umount.target
Before=local-fs.target umount.target ldconfig.service
ConditionVirtualization=!container
ConditionPathExists=!/usr/.noupdate
[Mount]
What=/dev/disk/by-label/OEM
Where=/usr/share/oem
Type=ext4

这个配置的效果等同于mkdir -p /usr/share/oem; mount -t ext4 -o nodev,commit=600 /dev/disk/by-label/OEM /usr/share/oem命令,但通过 Systemd 统一管理后,看起来更简洁了。

自动挂载文件系统

后缀名为“.automount”的 Unit 文件用于当用户访问某个目录时,自动将设定的文件系统挂载上去。在过去的 Linux 中,这个任务是由 autofs 服务完成的。

自动挂载点的 Unit 文件

自动挂载点的配置相对简单,但它必须与一个同名的“.mount”挂载点 Unit 文件配合使用。且这类 Unit 文件的命名同样必须与挂载目录转移后的名称一致的约定。

  • Where: 挂载点目录的绝对路径,这个路径应该与该 Unit 文件相对应。
  • DirectoryMode: 如果指定的挂载目录不存在时,Systemd 会将它自动创建出来。这个参数指定了目录的权限,默认是 0755。
  • TimeoutIdleSec: 这个配置可以指定一个超时时间。当挂载的目录在这段时间内都没有使用时,Systemd酒会尝试自动卸载它。将这个值设为0,则会禁用这个功能。默认这个功能是禁止的。

/etc/systemd/system/usr-share-oem.automount

1
2
3
4
5
6
7
8
[Unit]
Descrition=Test automount
[Automount]
Where=/usr/share/oem
[Install]
WantedBy=multi-user.target

交换分区(虚拟内存)

除了普通的挂载点,过去的 /etc/fstab 配置文件还有一项功能是交换分区的挂载。这部分功能在 Systemd 中是用专门的 Unit 文件完成的,它的后缀名为“.swap”。

作为一种特殊的挂载点,这类 Unit 文件的命名也必须遵守与挂载目录转义后名称一直的约定。

交换分区的 Unit 文件

  • What: 用于做交换分区的设备或文件的绝对路径。
  • Priority: 当有多个交换分区时,这个值回决定使用每个分区的优先级。
  • Options: 挂载交换分区的参数,这些参数会在实际挂载和卸载交换分区时,传递给 swapon 和 swapoff 命令。
  • TimeoutSec: 挂载交换分区的超时时间。如果在指定的时间内 swapon 命令仍然没有完成,则会被认为挂载失败。将这个值设为 0,则会禁用这个功能。默认这个功能是禁止的。

/etc/systemd/system/swapfile.swap

先创建一个大小为 2GB 的空白文件 swapfile。

1
2
3
$ sudo dd if=/dev/zero of=/swapfile bs=2048 count=1MB
$ sudo chmod 0600 /swapfile
$ sudo mkswap /swapfile
1
2
3
4
5
6
7
8
[Unit]
Description=Test swap file
[Swap]
What=/swapfile
[Install]
WantedBy=multi-user.target

使用 Cgroup

Cgconfig服务

要使用 cgroup,首先要确认系统中已安装 libcgroup软件包。(RedHat系)

1
# yum install libcgroup

cgconfig.conf文件

cgconfig.conf 文件包含两个主要类型的条目 ——— mount 和 group。挂载条目生成并挂载层级并将其作为虚拟文件系统,同时将子系统附加到那些层级中

1
2
3
4
5
6
7
8
9
10
11
12
# cat /etc/cgconfig.conf
mount {
cpuset = /cgroup/cpuset;
cpu = /cgroup/cpu;
cpuacct = /cgroup/cpuacct;
memory = /cgroup/memory;
devices = /cgroup/devices;
freezer = /cgroup/freezer;
net_cls = /cgroup/net_cls;
blkio = /cgroup/blkio;
}

挂载子系统的备选方法是使用 shell 命令:

1
2
# mkdir /cgroup/cpu
# mount -t cgroup -o cpu cpu /cgroup/cpu

组群条目使用一下语法设定子系统参数:

1
2
3
4
5
6
7
8
group <name>
[<permissions>]
<controller> {
<param name> = <param value>;
...
}
...
}

e.g. 为 sql 守护进程创建 cgroup,可为 sqladmin 组群中的用户在 cgroup 中添加任务,并让 root 用户修改子系统参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
group daemons/sql {
perm {
task {
uid = root;
gid = sqladmin;
} admin {
uid = root;
gid =root;
}
} cpu {
cpu.shares = 100;
}
}

以上配置对等的 shell 命令为:

1
2
3
4
# mkdir -p /cgroup/cpu/daemons/sql
# chown root:root /cgroup/cpu/daemons/sql/*
# chown root:sqladmin /cgfroup/cpu/daemons/sql/tasks
# echo 100 > /cgroup/cpu/daemons/sql/cpu.shares

在现有层级中附加或删除子系统

要在现有层级中添加子系统,从现有层级中取消层级或者将其移动到不同的层级中,请作为 root 编辑 /etc/cgconfig.conf 文件的 mount 部分,当 cgconfig 下次重启时,它会根据您指定的层级识别那些子系统。

使用 mount 命令挂载时加上 remount 参数,并列出新的子系统列表即可。

1
# mount -t cgroup -o remount,cpu,cpuset,... cpu_and_mem

卸载层级

直接使用 umount 即可卸载层级。不过需要注意的是:如果该层级目前为空(即它只包含 root cgroup),则在卸载它时会取消激活该层级。如果该层级包含任意其他 cgroup,该层级在内核中仍保持活跃,即使不再挂载它也是如此。

要删除层级,请确定您在卸载该层级前删除所有子 cgroup,或者使用 cgclear命令,它可在层级非空时也可取消激活层级。

cgroup操作

cgcreate

语法: cgcreate -t uid:gid -a uid:gid -g subsystems:path

  • -t(可选):指定用户(使用用户 ID,uid)和组群(使用组群 ID,gid)以便让这个 cgroup 拥有 tasks 伪文件。这个用户可在该 cgroup 中添加任务。
  • -a(可选):指定用户(使用用户ID,uid)和组群(使用组群ID,gid)以便这个 cgroup 拥有 tasks 外的所有伪文件。这个用户可修改这个 cgroup 中的任务对系统资源的访问。
  • -g:指定在其中创建 cgroup 的层级,格式为与那些层级关联的用逗号分开的 subsystems 列表。如果这个列表中的子系统在不同的层级中,则要在每个层级中都创建该组群。层级列表后是一个冒号,然后是与该层级有关的子组群 path。不要在该 path 中包含层级挂载点。

e.g. 在 cpu_and_mem 层级中一同挂载 cpu 和 memory 子系统的系统,并将 net_cls 控制器挂载到名为 net 的另一个层级中。

1
# cgcreate -g cpu,net_cls:/test-subgroup

备用方法

1
# mkdir /cgroup/hierarchy/name/child_name

cgdelete

使用 cgdelete 删除 cgroup,其语法与 cgcreate 类似。

cgdelete subsystems:path

-r 递归删除所有子组群

e.g.

1
# cgdelete cpu,net_cls:/test-subgroup

cgset

在可修改相关的 cgroup 的用户账户中 运行 cgset 命令设置子系统参数。

cgset -r parameter=value path_to_cgroup

parameter: 是要设定的参数,该参数与给定 cgroup 的目录中的文件对应。

value: 是为参数设定的值

path_to_cgroup: 是到相对该层级 root 的 cgroup 路径。

–copy-from path_to_source_cgroup path_to_target_cgroup: 将一个 cgroup 中的参数复制到另一个现有 cgroup 中。

备用方法

要直接在 cgroup 中设置参数,可使用 echo 命令将值插入相关子系统伪文件中。

1
# echo 0-1 > /cgroup/cpuset/group1/cpuset.cpus

cgclassify

cgclassify -g subsystems:path_to_cgroup pidlist

subsystems: 是用逗号分开的子系统列表,或者 * 启动与所有可用子系统关联的层级中的进程。

path_to_cgroup: 是到其层级中的 cgroup 的路径。

pidlist: 是用空格分开的进程识别符(PID)列表。

e.g. 将 PID 为 1701 和 1138 的进程移动到 cgorup 中的 group1/:# cgclassify -g cpu,memory:group1 1701 1138

备用方法

# echo 1701 > /cgroup/lab1/group/tasks

cgred

Cgred 是一个守护进程,它可根据在 /etc/cgrules.conf 中设定的参数将任务移动到 cgroup 中,条目可以使用以下两种格式之一:

  • user hierarchies control_group
  • user:command hierarchies control_group

e.g.

maria devices /usergroup/staff

这个条目指定任何属于名为 maria 用户的进程根据在 /usergroup/staff cgroup 中指定的参数访问设备子系统。

maria:ftp devices /usergroup/staff/ftp

该条目现在指定核实名为 maria 的用户使用 ftp 命令 ,自动将该进程移动到包含 devices 子系统的层级中的 /usergroup/staff/ftp cgroup 中。

/etc/cgrules.conf

@:当在 user 使用前缀时,代表是一个组群而不是一个单独用户。例如: @admins 是 admins 组群中的所有用户。

*: 代表“所有”。

%: 代表与以上行中项目相同的项目。

cgexec

cgexec -g subsystems:path_to_cgroup command arguments

subsystems:是用逗号分开的子系统列表或者 * 启动与所有可用子系统关联的层级的进程。

path_to_cgroup:是到与该层级相关的 cgroup 的路径。

commond:是要运行的命令。

arguments:是该命令所有参数。

–sticky:将所有子进程放在同一 cgroup 中。

备用方法

# echo $$ > /cgroup/lab1/group1/tasks

请注意:**退出用户后,现有 shell 中仍在 group1 cgroup 中。因此更好的方法应为:

# sh -c "echo \$$ > /cgroup/lab1/group1/tasks && USER

在控制组群中启动服务

您可在某个 cgroup 中启动某些服务。在 cgroup 中启动的服务必须:

  • 使用 /etc/sysconfig/servicename 文件
  • 使用 /etc/init.d/functions 的 daemon()功能启动该服务

要在某个 cgroup 中启动合格服务,请在 /etc/sysconfig 中编辑其文件,使该文件包含如下条目:CGROUP_DAEMON=”subsystem:control_group”

e.g. CGROUP_DAEMON="cpuset:daemons/sql"

获得有关控制组群的信息

查找某个进程

$ ps -O cgroup

$ cat /proc/PID/cgroup

查找子系统

$ cat /proc/cgroups

$ lssubsys -m subsystems

查找层级

$ tree /cgroup/

查找控制组群

$ lscgroup

$ lscgroup cpuset:adminusers

显示控制组群的参数

$ cgget -r parameter list_of_cgroups

其中 parameter 是包含子系统值的伪文件,list_of_cgroups 是用空格分开的 cgorup 列表。

cgclear

cgclear 命令将破坏所有层级中的所有 cgroup。如果没有在配置文件中保存这些层级,将无法再次构建它们。

Systemd 的服务管理模型

Systemd 是 CoreOS 采用的服务进程和系统资源管理工具。它最初的设计目标主要是克服传统 Linux 主流启动程序 SysV-init 的固有设计缺陷,提高系统的启动速度。 Systemd 的设计思路借鉴了 Mac OSX 系统的启动程序 Launchd,相比其他的 SysV-init 替代方案,例如 Ubuntu 的 Upstart,Systemd 的设计更加前卫。

事实上,Systemd 是一系列系统工具的集合,其作用也远远不仅是启动操作系统,它还接管了后台服务启动、结束、状态查询,以及日志归档、设备管理、电源管理、定时任务等许多指责,并支持通过特定事件(如插入特定 USB 设备)和特定端口数据触发的 On-demand 任务。在 CoreOS 的世界里,几乎所有与应用程序打交道的工作,都会交由 Systemd 进行管理,包括运行在容器中的服务。

Systemd的设计理念

  • 尽可能启动更少的进程
  • 尽可能将更多的进程并行启动
  • 采用 CGroup 跟踪和管理进程的生命周期
  • 统一管理服务日志

Systemd的服务管理

Unit

Unit 是 Systemd 管理服务的基本单元,可以认为每个服务就是一个 Unit,并使用一个 Unit 文件定义。在 Unit 文件中需要包含相应服务的描述、属性以及需要运行的命令。

在 CoreOS 中运行服务的命令通常是一系列的容器操作,而将具体的服务进程封装在容器中。

Target

Target 是 Systemd 中用于指定服务启动组的方式,相当于 SysV-init 中的“运行级别”。每次系统启动时都会运行与当前系统具有相同级别 Target 关联的所有服务,如果服务不需要跟随系统自动启动,则完全可以忽略这个 Target 的内容。

通常来说,大多数 Linux 用户平时使用的都是“多用户模式”这个级别,对应的 Target 值为 “multi-user.target”,它有一个等效的可用值是“default.target”。

Hello Systemd

在 CoreOS 中,用户的自定义服务配置文件通常放在 /etc/systemd/system目录中。进入这个目录,新建一个名为“hello.service”的文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=Hello Systemd
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill busybox1
ExecStartPre=-/usr/bin/docker rm busybox1
ExecStartPre=/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --name busybox1 busybox /bin/sh -c "while true; do echo Hello Systemd; sleep 1; done"
ExecStop=/usr/bin/docker stop busybox1
ExecStopPost=/usr/bin/docker rm busybox1
[Install]
WantedBy=multi-user.target

这里需要注意两个地方:

  1. ExecStart 属性只能包含一条主要命令,而在这个属性的前后可以分别使用 ExecStartPre 和 ExecStartPost 指定更多的辅助命令;ExecStop同理。
  2. 有些辅助命令会加上一个减号(-),表示忽略这些命令的出错。

日志管理

Systemd 通过其标准日志服务 Journald 将其管理的所有后台进程打印到 std:out(即控制台)的输出重定向到了日志文件。

日志文件是二进制格式的,必须使用 Journald 提供的配套程序 journalctl 来查看。

Journalctl

Journalctl 的使用非常简单,默认不带任何参数时会输出系统和所有后台进程的混合日志,可以通过一些参数选择性地过滤所需的内容。

  • –dmesg:内核输出的日志
  • –system:各类系统服务的控制台输出
  • –unit xxx.service:用户指定的服务日志

高级用法:

  • –follow:实时跟踪日志输出
  • –since和–unit:指定显示的日志时间区间(e.g. –since “2015-07-20 12:00:00” –until “2015-07-20 12:30:00”),–since=today可以直接打印当天输出的日志。
  • –reverse:反向输出日志
  • –lines:查看最新的N行日志(e.g. –lines n)
  • –output:改变输出日志的格式(e.g. –output json-pretty)

另外,除了使用 –unit 参数指定 Unit 名称外,还可以指定程序名称和指定进程 PID。

  • $ journalctl /usr/lib/systemd/systemd
  • $ journalctl _PID=1

日志大小限制

默认日志最大限制为所在文件系统容量的 10%。即:如果/var/log/journal存储在 50GB 的根分区中,那么日志最多存储 5GB 数据。

可以通过修改 /etc/systemd/journald.conf中的 SystemMaxUse来指定该最大限制。比如SystemMaxUse=50M

服务的生命周期

  1. 当一个新的 Unit 文件被放入 /etc/systemd/system//usr/lib/systemd/system/目录中时,它是不会被 Systemd 识别到的;($ systemctl list-unit-files)
  2. systemctl start|enable 激活;($ systemctl list-units )
  3. 使用 systemctl start 命令可以启动指定的服务,启动时会一次执行定义在 Unit 文件中的 ExecStartPre、ExecStart、ExecStartPost命令
  4. 使用 ststmectl stop 命令可以结束指定的服务,结束时会依次执行定义在 Unit 文件中的 ExecStopPre、ExecStop、ExecStopPost命令;
  5. systemctl restart 命令相当于先结束指定的服务,然后立即重新启动它;
  6. systemctl kill命令会立即杀死服务,而不会执行 Unit 中指定的结束命令;
  7. systemctl enable xxx.service 设置开机启动;
  8. systmectl disable xxx.service 取消开机启动;
  9. systemctl daemon-reload 重新加载 Unit 文件;
  10. systemctl reset-failed 移除已经被标记为丢失的 Unit 文件。

服务的Unit文件

服务的 Unit 文件可以分为三个配置区段,其中 Unit 段和 Install 段是所有 Unit 文件通用的,用于配置服务(或其他系统资源)的描述、依赖和随系统启动方式,而 Service 段则是服务类型的 Unit 文件(后缀为.service)特有的,用于定义服务的具体管理和操作方法。

配置区段的常用参数

  • Unit
    • Description: 一段描述这个 Unit 文件的文字。
    • Documentation: 指定服务的文档,可以是一个或多个文档的路径。
    • Requires: 依赖的其他 Unit 列表,依赖服务启动失败会导致这个服务被终止。
    • Wants: 与 Requires 相似,但不考虑依赖服务是否启动成功。
    • After: 与 Requires 相似,但是在列出的服务都启动完成后才会启动当前服务。
    • Before: 与 After 相反。
    • BindsTo: 与 Requires 相似,但是关联性更强。
    • Part Of: 这是一个 BindTo 作用的子集,仅在列出的任何模块失败或重启时,终止或重启当前服务,而不会随列出的模块的启动而启动。
    • OnFailure: 当这个模块启动失败时,就会自动启动列出的每个模块。
    • Conflicts: 与这个模块有冲突的模块,如果列出的模块中有已经在运行的,这个服务就不能启动;反之亦然。
  • Install
    • WantedBy: 和前面的 Wants 相似,只是后面列出的不是服务所依赖的模块,而是依赖当前服务的模块。
    • RequiredBy: 和前面的 Requires 相似,只是后面列出的不是服务所依赖的模块,而是依赖当前服务的模块。
    • Also: 当这个服务被 enable/disable 时,将自动 enable/disable 后面列出的每个模块。
  • Service
    • Type: 服务的类型,常用的有 simple 和 forking。
    • RemainAfterExix: 值为 true 或 false(也可以写为 yes 或 no),默认为 false。当配置值为 true 时,Systemd 只会负责启动服务进程,之后即便服务进程退出了,Systemd也仍然会认为这个服务还在运行中。这个配置主要是提供给一些并非常驻内存,而是启动注册后立即退出,燃火等待消息按需启动的特许类型服务使用的。
    • ExecStart: 这个参数是疾呼每个”.service”文件都会有的,指定服务启动的主要命令,在每个配置文件中只能使用一次。
    • ExecStartPre: 指定在启动执行 ExecStart 命令前的准备工作,在同一个配置文件中可以有多个。
    • ExecStartPost: 指定在启动执行 ExecStart 命令后的首位工作,在同一个配置文件中可以有多个。
    • TimeoutStartSec: 启动服务时的超时时间。Docker 第一次运行时可能会需要从网络上下载服务的镜像文件,因此造成比较严重的延时,建议将值指定为 0,从而关闭检测。
    • ExecStop: 停止服务所需要执行的主要命令,在每个配置文件中只能有一个。
    • ExecStopPost: 指定在 ExecStop 命令执行后的收尾工作,在同一个配置文件中可以有多个。
    • TimeoutStopSec: 停止服务时的超时时间。
    • Restart: 这个值用语指定在什么情况下需要重启服务进程。常用的值有 no、on-success、on-failure、on-abnormal、on-abort 和 always。默认值为 no。如下表格表示分别在哪些情况下,服务会被重新启动。
    • RestartSec: 如果服务需要被重启,这个参数的值为服务被重启前的等待秒数。
    • ExecReload: 重新加载服务所需执行的主要命令。
    • Environment: 为服务添加环境变量。
    • EnviromentFile: 指定加载一个包含服务所需的环境变量列表的文件,文件中的每一行都是一个环境变量的定义。
    • Nice: 服务的进程优先级,值越小优先级越高,默认为0。其中-20 为最高优先级,19为最低优先级。
    • WorkingDirectory: 指定服务的工作目录。
    • RootDirectory: 指定服务进程的根目录。如果配置了这个参数,服务将无法访问指定目录以外的任何文件。
    • User: 指定运行服务的用户,会影响服务对本地文件系统的访问权限。
    • Group: 指定运行服务的用户组,会影响服务对本地文件系统的访问权限。
    • MountFlags: 之歌值其实是服务的 Mount Namespace 的配置,会影响服务进程上下文中挂载点的信息,即服务是否会继承主机上已有的挂载点,以及如果服务运行时执行了挂载或卸载设备的操作,是否会真实地在主机上产生效果。可选值是 shared、slava 或 private。
    • LimitCPU/LimitSTACK/LimitNOFILE/LimitNPROC 等,限制特定服务可用的系统资源量。

Systemd 中用于兼容 SysV-init 运行级别的目标 Unit 文件

SysV-init 运行级别 Systemd 运行目标 注释
0 runlevel0.target, poweroff.target 关闭系统
1 runlevel1.target, rescue.target 单用户模式
2 runlevel2.target, multi-user.target 用于定义/域特定运行级别。默认等同于级别 3
3 runlevel3.target, multi-user.target 多用户,无图形界面,用户可以通过终端或网络登录
4 runlevel4.target, multi-user.target 用于定义/域特定运行级别。默认等同于级别 3
5 runlevel5.target, graphical.target 多用户,图形界面,通常为所有运行级别 3 的服务并启动图形界面服务
6 runlevel6.target, reboot.target 重启
emergency emergency.target 急救模式(Emergency shell)

Service 段的 Restart 属性

服务退出原因 no always on-failure on-abnormal on-abort on-success
正常退出
异常退出
启动/停止超时
被异常杀死

Unit 文件占位符

在 Unit 文件中,有时会需要使用到一些与运行环境有关的信息,例如节点 ID、运行服务的用户等。这些信息可以使用占位符来表示,然后在实际运行时被动态地替换为实际的值。

占位符 作用
%n 完整的 Unit 文件名,包括 .service 后缀名
%p Unit 模版文件名中在 @ 符号之前的部分,不包括 @ 符号
%i Unit 模版文件名中在 @ 符号之后的部分,不包括 @ 符号和 .service 后缀名
%t 存放系统运行时文件的目录,通常都是 “/run”
%u 运行服务的用户们,如果 Unit 文件中没有指定,则默认为 root
%U 运行服务的用户 ID
%h 运行服务的用户 Home 目录,即 ${HOME} 环境变量的值
%s 运行服务的用户默认 Shell 类型,即 ${SHELL} 环境变量
%m 实际运行的节点的 Machine ID,对于运行位置敏感的服务比较有用
%b Boot ID,这是一个随机数值,每个节点各不相同,并且每次节点重启时都会改变
%H 实际运行节点的主机名
%v 内核版本,即 “uname -r”命令输出的内容
%% 在 Unit 模版文件中表示一个普通的百分号

Unit 模版

在现实中国年,往往有一些应用需要被复制多份运行,Systemd 定义了一种特殊的服务 Unit 文件,称之为 “Unit 模版”。

Unit 模版文件的写法与普通的服务 Unit 文件几本相同,不过 Unit 模版的文件名是以 @ 符号结尾的。通过模版启动服务实例时,需要在其文件名的 @ 字符后面添加一个参数字符串。

apache@.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=My Advanced Service Template
After=etcd.service docker.service
[Service]
TimeoutStartSec=0
ExecStartpre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStart=/usr/bin/docker run --name apache%i -p %i:80 coreos/apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStartPost=/usr/bin/etcdctl set /domains/example.com/%H:%i running
ExecStop=/usr/bin/docker stop apache1
ExecStopPost=/usr/bin/docker rm apache1
ExecStopPost=/usr/bin/etcdctl rm /domains/example.com/%H:%i
[Install]
WantedBy=multi-user.target

模版服务的启动也与普通的托管于 Systemd 的服务大致相同。

$ sudo systemctl start apache@8080.service

Cgroup 简介

Cgroupcontrol group的缩写,是Linux内核提供的一种可以限制、记录、隔离进程组(process group)所使用的物理资源(如:cpu,memory,IO等等)。

使用 cgroup,系统管理员可耕具体地控制对系统资源的分配、优先顺序、拒绝、管理和监控。可更好地根据任务和拥护分配硬件资源,提高总体效率。

Cgroup 最初由google的工程师提出,后来被整合进Linux内核。Cgroup 也是LXC(Linux container)为实现虚拟化所使用的资源管理工具。最初的 Docker 版本其实就是在LXC上集成一些工具集,后来才重新设计自己的虚拟化层,发布了libcontainer

如何管理cgroup

Cgroup 是分层管理的,类似进程,且子 cgroup 会继承其上级 cgroup 的一些属性。但这两个模式也有不同。

Linux 进程模式

Linux 系统中的所有进程都是通用父进程 init 的子进程 ,该进程在引导时由内核执行并启动其它进程(这些进程会按顺序启动其子进程)。因为所有进程都归结到一个父进程,所以 Linux 进程模式是一个单一层级结构,或者树结构。

另外,init 之外的每个 Linux 进程都会继承其父进程的环境(比如 PATH 变量)和某些属性(比如打开文件描述符)。

Cgroup 模式

Cgroup 与进程在以下方面类似:

  • 它们是分级的
  • 子 cgroup 会集成其 cgroup 的某些属性

根本的不同是在某个系统中可同时存在不同的分级 cgroup。如果 Linux 进程模式是进程的单一树模式,那么 cgroup 模式是一个或者更多任务的独立、未连接树(例如:进程)。

需要多个独立 cgroup 分级,因为每个分级都会附加到一个或者多个子系统中。

子系统

以 RedHat6 为例,系统提供9个 cgroup 子系统,根据名称和功能列出如下。

  • blkio: 这个子系统为块设备设定输入/输出限制,比如物理设备(磁盘,固态硬盘,USB等等)。
  • cpu: 这个子系统使用调度程序提供对 CPU 的 cgroup 任务访问。
  • cpuacct: 这个子系统自动生成 cgroup 中任务所使用的 CPU 报告。
  • cpuset: 这个子系统为 cgroup 中的任务分配独立 CPU(在多核系统)和内存节点。
  • devices: 这个子系统可允许或者拒绝 cgroup 中的任务访问设备。
  • freezer: 这个子系统挂起或者恢复 cgroup 中的任务。
  • memory: 这个子系统设定 cgroup 中任务使用的内存限制,并自动生成由那些任务使用的内存资源报告。
  • net_cls: 这个子系统使用等级识别符(classid)标记网络数据包,可允许 Linux 流量控制程序(tc)识别从具体 cgroup 中生成的数据包。
  • ns: 命名空间子系统。

子系统、层级、控制组群和任务的关系

在 cgroup 术语中系统进程称为任务。

Rule 1

任何单一子系统(比如 cpu)最多可附加到一个层级中。

结果是,cpu 子系统永远无法附加到两个不同的层级。

Rule 2

单一层级可附加一个或者多个子系统

结果是,cpu 和 memory 子系统(或者任意数目的子系统)都可附加到单一层级中,只要每个子系统不再附加到另一个层级即可。

Rule 3

每次在系统中创建新层级时,该系统中的所有任务都是那个层级的默认 cgroup(我们称之为 root cgroup)的初始成员。任何创建的单一层级,该系统中的每个任务都可以是那个层级中唯一一个 cgroup 的成员。单一任务可以是在多个 cgroup 中,只要每个 cgroup 都在不同的层级中即可。只要某个任务成为同一层级中第二个 cgroup 成员,就会将其从那个层级的第一个 cgroup 中删除。

一个任务永远不会同时位于同一层级的不同 cgroup 中。

结果是,如果 cpu 和 memory 子系统都附加到名为 cpu_and_mem的层级中,且 net_cls 子系统是附加到名为 net 的层级中,那么运行的 httpd 进程可以是 cpu_and_mem 中任意 cgroup 的成员,同时也是 net 中任意 cgroup 的成员。

httpd 进程所在 cpu_and_mem 中的 cgroup 可将其 CPU 时间限制为分配给其它进程时间的一半,并将其内存用量限制为最多 1024MB。另外,net中的 cgroup 还可将其传输速率限制为 30MB/s。

首次创建层级时,该系统中的每个任务都至少是一个 cgroup 的成员,即 root cgroup。因此每当使用 cgroup 时,每个系统任务总是至少在一个 cgroup 中。

Rule 4

系统中的任意进程(任务)都将自己分支创建子进程(任务)。该子任务自动成为其父进程所在 cgroup 的成员。然后可根据需要将该子任务移动到不同的 cgroup 中,但开始时它总是继承其父任务的 cgroup(进程术语中称其为“环境”)。

cpu_and_mem 层级中名为 halt_cpu_1gb_max 的 cgroup 成员的任务,以及 net 层级中 cgroup trans_rate_30 的成员。当 httpd 进程将其自身分为几个分支时,其子进程会自动成为 half_cpu_1gb_max cgroup 和 trans_rate_30 cgroup 的成员。它会完全继承其父任务所属的同一 cgroup。

此后,父任务和子任务就彼此完全独立:更改某个任务所属 cgroup 不会影响到另一个。同样更改父任务的 cgroup 也不会以任何方式影响其子任务。总之:所有子任务总是可继承其父任务的同一 cgroup 的成员关系,但之后可更改或者删除那些成员关系。

资源管理实施

  • 因为某个任务可属于任一层级中的单一 cgroup,所以只有一个方法可让单一子系统限制或者影响任务。这是合理的:是一个功能,而不是限制。
  • 可以将几个子系统分组在一起以便它们可影响单一层级中的所有任务。因为该层级中的 cgroup 有不同的参数设定,因此会对那些任务产生不同的影响。
  • 有时可能需要重构层级。例如:从附加了几个子系统的层级中删除一个子系统,并将其附加到不同的层级中。
  • 反正,如果从不同层级中分离子系统的需求降低,则可以删除层级并将其子系统附加到现有层级中。
  • 这个设计允许简单的 cgroup 使用,比如为单一层级中的具体任务设定几个参数 ,单一层级可以是只附加了 cpu 和 memory 子系统的层级。
  • 这个设计还允许高精度配置:系统中的每个任务(进程)都可以是每个层级的成员,每个层级都有单一附加的子系统。这样的配置可让系统管理员绝对控制每个单一任务的所有参数。

Curl 发送邮件

关于curl,大家都知道它可用来访问web页面,下载文件等等。其实它的功能远不止这么点,它支持众多协议,其中就包括发送邮件的SMTP协议。

确认curl是否支持SMTP

首先确认你的curl是否支持smtp。

1
# curl-config --protocols |grep SMTP

如果不支持smtp协议,那么重新安装curl。

安装高版本curl

使用yum安装的curl一般不支持smtp协议,接下来我们使用源码包来安装。

1
2
3
4
5
6
7
# cd /usr/local/src
# wget https://github.com/bagder/curl/archivemaster.zip
# unzip master.zip
# cd curl-master
# ./buildconf
# ./configure
# make && make install

安装好后再次确认是否支持smtp。

使用curl发送邮件

编写邮件内容

1
2
3
4
5
6
# cat mail.txt
From:from@xxx.com
To:to@xxx.com
Subject: curl发送邮件标题
邮件内容。。。

发送邮件

1
# /usr/local/bin/curl -s --url "smtp://smtp.xxx.com --mail-from "from@xxx.com" --mail-rcpt "to@xxx.com" --upload-file mail.txt --user "from@xxx.com:PASSWORD"

参数说明

–url: smtp地址

–mail-from: 发件人邮箱

–mail-rcpt: 收件人邮箱

–upload-file: 信件内容,包含发件人、收件人、标题、内容

–user: 账号密码,中间用冒号分割

curl更多协议

curl支持众多协议,详细内容可使用如下命令查看。

1
# /usr/local/bin/curl-config --protocols

zabbix curl发邮件脚本

我们通常使用sendEmail来发送告警,下面分享一个zabbix使用curl发送告警邮件的脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash
# -------------------------------------------------------------------------------
# FileName: zabbix_curl_sendmail.sh
# Revision: 1.0
# Date: 2016/02/18
# Author: dingmingk
# Email: dingmingk@gmail.com
# Website: www.dingmingk.com
# Description: use curl send email
# Notes: ~
# -------------------------------------------------------------------------------
# Copyright: 2015 (c) dingmingk
# License: GPL
MAIL_FROM='from@xxx.com'
MAIL_TO=$1
MAIL_SUBJECT=$2
MAIL_CONTENT=$3
MAIL_CONTENT_FILE="/tmp/`/bin/date +%s`.txt"
MAIL_SMTP='smtp://smtp.xxx.com'
MAIL_USER='from@xxx.com'
MAIL_PASSWORD='PASSWORD'
# create mail content file
echo "From:${MAIL_FROM}
To:$1
Subject: $MAIL_SUBJECT
$MAIL_CONTENT "> ${MAIL_CONTENT_FILE}
# send mail
/usr/local/bin/curl -s --url "${MAIL_SMTP}" --mail-from "${MAIL_FROM}" --mail-rcpt ${MAIL_TO} --upload-file ${MAIL_CONTENT_FILE} --user "${MAIL_USER}:${MAIL_PASSWORD}"
# delete mail content file
rm ${MAIL_CONTENT_FILE}

Git 和声

冲突解决

只要有合并就可能会有冲突。

自动合并

修改不同的文件

有时候我们在push代码到远程仓库时,会遇到一些小麻烦。

git_part3_1.png

图为出现非快进式推送时的日志。

这时我们查看当前版本库和远程版本库的commit哈希值,可以发现二者的不同,即出现了非快进式推送。

git_part3_2.png

非快进式推送(nor-fast-forwardupdates) 在远程版本库和当前版本库内容不一致时推送所致,引起原因一般为在多成员协同工作下,其他用户在当前用户版本库上次commit和本次commit之间向远程版本库执行了推送所致。

解决方法

  • 先执行pull,再继续push,即合并后推送。这才是多成员协同工作推荐的方式。
  • 使用 git push origin master --force强制推送。这种方式存在覆盖掉其他人提交的危险,不推荐使用。

修改相同文件的不同区域

解决方法基本如上。但是如果追溯一下文件每一行的来源,可以看到分别是 dingmingk 和 root 用户更改的第一和最后的一行。

git_part3_3.png

同时更改文件名和文件内容

解决方法如上。最新版本的文件名和内容都为修改后。

冲突解决

如果两个用户修改了同一文件的同一区域,则在合并的时候会遇到冲突而导致合并过程中端。这是因为 Git 并不能越俎代庖地替用户做出决定,而是把决定权交给用户。在这种情况下,Git 标识出合并冲突,等待用户对冲突做出抉择。

git_part3_4.png

如图所示,特殊标识 <<<<<<<(七个小于号)和 =======(七个等号)之间的内容是当前分支所更改的内容。特殊标识 =======(七个等号)和 >>>>>>>(七个大于号)之间的内容是所合并的版本更改内容。

冲突解决的实质就是通过编辑操作,将冲突标识符所标识的冲突内容替换为合适的内容,并去掉冲突标识符。编辑完后再重新提交即可。

里程碑

里程碑即 Tag,是人为对提交进行的命名。相比于 Git 默认的提交ID,使用一个直观的表意的字符串会方便得多。

1
2
3
4
5
git tag <tagname> [<commit>]
git tag -a <tagname> [<commit>]
git tag -m <msg> <tagname> [<commit>]
git tag -s <tagname> [<commit>]
git tag -u <key-id> <tagname> [<commit>]
  • 第一种是创建轻量级里程碑。
  • 第二种和第三种想同,都是创建带说明的里程碑。其中第三种直接通过 -m 参数提供里程碑创建说明。
  • 第四种和第五种相同,都是创建带 GNUPG 签名的里程碑。其中第五种用 -u 参数选择指定的私钥进行签名。
  • 创建里程碑需要输入里程碑的名字()和一个可选的提交ID()。如果没有提供提交ID,则基于头指针 HEAD 创建里程碑。

轻量级里程碑

带说明的里程碑

带签名的里程碑

删除里程碑

里程碑命名规范

Git 分支

分支命令

1
2
3
4
5
6
7
git branch
git branch <branchname>
git branch <branchname> <start-point>
git branch -d <branchname>
git branch -D <branchname>
git branch -m <oldbranch> <branchname>
git branch -M <oldbranch> <newbranch>
  • 第一种用于显示本地分支列表。当前分支在输出中会显示为特别的颜色,并用星号“*”标识出来。
  • 第二种和第三种用于创建分支。用法2基于当前头指针(HEAD)指向的提价创建分支,新分支的分支名 。第三种基于提交 创建新分支,新分支的分支名为
  • 第四种和第五种用户删除分支。第四种在删除分支 时会检查所要删除的分支是否已经合并到其它分支种,否则拒绝删除。第五种会强制删除分支。
  • 第六种和第七种用于重命名分支。如果版本库中已经存在名为 的分支,第六种会拒绝执行重命名,而第七种会强制执行。

分支变基

1
2
$ git checkout develop
$ git rebase master

这两条命令会把你的 develop 分支里的每个提交(commit)取消掉,并且把它们临时保存为补丁(这些patch放到 .git/rebase 目录中),然后把 develop 分支更新为最新的 master 分支,最后把保存的这些补丁应用到 develop 分支上。

远程版本库

代码回滚

本地代码库回滚

git reset 命令回滚有三种方式:

  • mixed: 默认方式。它回退到某个版本,只保留源码,回退commit和index信息。
  • soft: 只回退commit信息,不会恢复index file一级。
  • hard: 彻底回退到某个版本,本地的源码也会变为上一个版本的内容。
1
2
3
4
5
* $ git reset --[hard|soft|mixed] <commit> 回滚所有内容到某个提交
* $ git reset --[hard|soft|mixed] HEAD^ 回滚所有内容到上一个版本
* $ git reset --[hard|soft|mixed] HEAD^^ xxx 回滚xxx这个文件到上两个版本
* $ git reset --[hard|soft|mixed] HEAD~3 回滚所有内容到第三个版本
* $ git reset --[hard|soft|mixed] origin/master 回滚到和远程一样

远程代码库回滚

远程代码库回滚比本地回滚要复杂一些,建议操作前先备份分支。

操作步骤:

1
2
3
4
5
6
7
1. $ git checkout <branch> 切换到分支
2. $ git pull 拉取最新代码
3. $ git branch <branch> <branch_backup> 备份分支
4. $ git reset --hard <commit> 回滚本地代码库
5. $ git push origin :<branch> 删除远程分支
6. $ git push origin <branch> 将本地分支推送到远程
7. $ git push origin :<branch_backup> 删除远程备份分支

反转提交

1
2
* $ git revert <commit> 用一次新的反转提交来消除一个历史提交所做的任何修改
$ git revert -m 1 <commit> “-m 1”参数表示某次历史提交的父提交

SaltStack 用户管理

SaltStack拥有非常多的模块,功能很强大。其中salt.states.user这个模块可以帮助我们更方便管理服务器用户权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root:
user.present:
- shell: /bin/bash
- passwd: 'xxxxxxxxxxxxxxxxxxx'
- uid: 0
- gid: 0
- require:
- group: root
group.present:
- gid: 0
admin:
user.absent
/root/.ssh/authorized_keys:
file.managed:
- source: salt://files/.ssh/root_authorized_keys
- user: root
- group: root
1
2
$ openssl passwd -1 -salt 'root'
Password:

输入密码后会生成一串md5值,替换上面配置文件中的xxx。

1
$ salt 'target' state.sls user.sls

推送到客户端生效。

另外Salt有专门的模块来管理SSH证书,这里先用文件管理的土办法。TODO

Nginx 简单用户认证

有时候我们会有一些Web系统,默认不带登录认证功能,除了限制访问IP之外,还需要添加一个登录认证,Nginx自带的 ngx_http_auth_basic_module模块可以帮我们实现一个简单的用户登录验证功能。

语法: auth_basic string | off;

默认值: off

配置段: http, server, location, limit_except

默认不开启认证,如果后面跟上字符,这些字符会在弹窗中显示。

1
2
$ echo -n 'dingmingk:' >> conf/site_pass
$ openssl passwd passwd123 >> conf/site_pass

生成一个用户名为dingmingk密码为passwd123的用户认证。

1
2
3
4
5
6
7
8
9
10
11
server {
server_name www.dingmingk.com
index index.html index.php;
root /data/www.dingmingk.com;
location / {
auth_basic "Please input your Username&Password.";
auth_basic_user_file conf/site_pass;
}
}

Safari浏览器打开如下:
nginxAuth1
认证错误界面:
nginxAuth2

Nginx map 使用方法

map指令是ngx_http_map_module模块提供的,默认情况下nginx有加载这个模块,除非手动--without-http_map_module

ngx_http_map_module模块可以创建变量,这些变量的值与另外的变量值相关联。允许分类或者同时映射多个值到多个不同值并储存到一个变量中,map指令用来创建变量,但是仅在变量被接受的时候执行视图映射操作,对于处理没有引用变量的请求时,这个模块并没有性能上的缺失。

语法: map $var1 $var2 {…}

默认值: -

配置段: http

map为一个变量设置的映射表。映射表由两列组成,匹配模式和对应的值。

在map块里的参数指定了源变量值和结果值的对应关系。

匹配模式可以是一个简单的字符串或者正则表达式,使用正则表达式要用(‘~’)。

一个正则表达式如果以“~”开头,表示这个正则表达式对大小写敏感。以“~*”开头,表示这个正则表达式对大小写不敏感。

1
2
3
4
5
map $http_user_agent $agent {
default "";
~curl curl;
~*apachebench" ab;
}

正则表达式里可以包含命名捕获和位置捕获,这些变量可以跟结果变量一起被其它指令使用。

1
2
3
4
5
map $uri $value {
/dingmingk_com /index.php;
~^/dingmingk_com/(?<suffix>.*)$ /boy/;
~/fz(/.*) /index.php?;
}

不能在map块里面引用命名捕获或位置捕获变量
~^/dingmingk_com/(.*) /boy/$1; 这样会报错`nginx:[emerg] unknown variable。

如果源变量值包含特殊字符如’~’,则要以’\’来转义。

1
2
3
4
map $http_referer $value {
Mozilla 111;
\~Mozilla 222;
}

如果变量可以是一个字符串也可以是另外一个变量。

1
2
3
4
map $num $limit {
1 $binary_remote_addr;
0 "";
}

map指令有三个参数:

  • default: 没有匹配结果将使用的默认值。如果没有设置default,将会用一个空的字符串作为默认的结果。
  • hostnames: 允许用前缀或者后缀掩码指定域名作为源变量值,这个参数必须写在映射列表的最前面。
  • include: 包含一个或多个含有映射值的文件。

如果匹配到多个特定的变量,如掩码和正则同时匹配,那么会按照下面的顺序进行选择:

  1. 没有掩码的字符串
  2. 最长的带前缀的字符串,如“*.example.com”
  3. 最长的带后缀的字符串,如“mail.*”
  4. 按顺序第一个先匹配的正则表达式(在配置文件中体现的顺序)
  5. 默认值

语法: map_hash_bucket_size size;

默认值: 32,64,128

配置段: http

指定一个映射表中的变量在哈希表中的最大值,这个值取决于处理器的缓存。

语法: map_hash_max_size size;

默认值: 2048

配置段: http

设置映射表对应的哈希表的最大值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
http {
map $http_user_agent $agent {
~curl curl;
~*chrome chrome;
}
server {
listen 80;
server_name test.dingmingk.com;
location /hello {
default_type text/plain;
echo http_user_agent: $http_user_agent;
echo agent: agent:$agent;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
http {
map $uri $match {
~^/hello/(.*) http://www.dingmingk.com/;
}
server {
listen 80;
server_name test.dingmingk.com;
location /hello {
default_type text/plain;
echo uri: $uri;
echo match: $match;
echo capture: $1;
echo new: $match$1;
}
}
}