CentOS6使用二进制安装mariadb

MariaDB 是一个采用Maria 存储引擎的MySQL分支版本,是由原来 MySQL 的作者Michael Widenius创办的公司所开发的免费开源的数据库服务器。MariaDB是目前最受关注的MySQL数据库衍生版,也被视为开源数据库MySQL的替代品。除了使用Linux 各发行版供应商的程序包安装,也可以选择基于二进制格式的程序包进行安装。具体安装步骤如下:

1、下载二进制源码

官网下载地址http://downloads.mariadb.org

2、创建系统用户

[root@Centos6 ~]# groupadd -r -g 36 mysql
[root@Centos6 ~]# useradd -r -u 36 -g 36 mysql

3、准备二进制程序

解压缩包到/usr/local

[root@Centos6 ~]# tar xf /root/mariadb-5.5.57-linux-x86_64.tar.gz -C /usr/local/

创建软链接并修改目录属组为mysql

[root@Centos6 local]#cd /usr/local
[root@Centos6 local]#ln -sv  mariadb-5.5.57-linux-x86_64/ mysql
    `mysql' -> `mariadb-5.5.57-linux-x86_64/'
[root@Centos6 local]# chown -R root:mysql /usr/local/mysql/

4、准备mysql数据存储目录

建议把mysql数据存在基于逻辑卷的单独分区

[root@Centos6 local]#lvcreate -L 20G -n mydata vg_centos6
[root@Centos6 local]#mkfs.ext4 /dev/vg_centos6/mydata

设置开机自动挂载逻辑卷mydata到/mydata

[root@Centos6 local]mkdir /mydata
[root@Centos6 local]vim /etc/fstab
    /dev/vg_centos6/mydata /mydata                  ext4    defaults        0 0
[root@Centos6 local]mount -a

创建存储目录/mydata/data并修改属主属组为mysql

[root@Centos6 local]chown mysql:mysql /mydata/data

5、创建数据库文件

安装包提供了自动生成数据库的脚本/usr/local/mysql/scripts/mysql_install_db,在/usr/local/mysql目录下运行该脚本

[root@Centos6 mysql]# ./scripts/mysql_install_db --user=mysql --datadir=/mydata/data   
[root@Centos6 mysql]# ./scripts/mysql_install_db --help                               <-- 可以查看脚本帮助
[root@Centos6 mysql]# ls /mydata/data
aria_log.00000001  aria_log_control  mysql  performance_schema  test

6、准备mysqld程序配置文件

​配置文件查找次序: /etc/my.cnf – > /etc/mysql/my.cnf– >– default-extrafile=/PATH/TO/CONF_FILE(第5步中脚本选项指定的配置文件) – > ~/. my.cnf

安装包提供了几种不同配置的模板配置文件,位于目录/usr/local/mysql/suport-files/;可以根据数据库的大小及服务器配置等选择合适的模板进行修改

[root@Centos6 mysql]# mkdir /etc/mysql
[root@Centos6 mysql]# cp support-files/my-large.cnf /etc/mysql/my.cnf
[root@Centos6 mysql]# vim /etc/mysql/my.cnf
datadir = /mydata/data                     <--指定数据库文件存储目录
innodb_file_per_table = on                 <--数据库中各表格以单个文件存储
skip_name_resolve = on                     <--禁止主机名解析

7、准备日志文件

Centos6–>/var/log/mysqld.log

注意在Centos7里自动生成,不用手动创建–>/var/log/mariadb/mariadb.log

[root@Centos6 mysql]# touch /var/log/mysqld.log
[root@Centos6 mysql]# chown mysql:mysql /var/log/mysqld.log

8、准备服务脚本,并启动服务

[root@Centos6 mysql]# cp support-files/mysql.server /etc/rc.d/init.d/mysqld
[root@Centos6 mysql]# chkconfig --add mysqld
[root@Centos6 mysql]# chkconfig --list mysqld
[root@Centos6 ~]# vi /etc/profile.d/my.sh                       <--创建系统配置文件,将可执行程序mysql路径加入PATH变量
export PATH=/usr/local/mysql/bin/:$PATH
[root@Centos6 mysql]#service mysqld start

9、运行mysql命令–>交互式客户端程序

[root@Centos6 ~]#mysql                          <-- 默认空密码登录
MariaDB [mysql]> use mysql
Database changed
MariaDB [mysql]> select user,host,password from user;
+------+-----------+----------+
| user | host      | password |
+------+-----------+----------+
| root | localhost |          |
| root | centos6.9 |          |
| root | 127.0.0.1 |          |
| root | ::1       |          |
|      | localhost |          |              <-- 表示允许匿名登录
|      | centos6.9 |          |
+------+-----------+----------+
6 rows in set (0.01 sec)
  • mysql用户账号由两部分组成:’USERNAME’@’HOST’

  • HOST用于限制此用户可通过哪些远程主机连接mysql服务(限制客户端)

  • HOST支持CIDR IP表示法;也支持使用通配符:

  • % 匹配任意长度的任意字符 eg: 192.168.%.%

  • _ 匹配任意单个字符

10、安全初始化

从文章第9步可以看出,数据库默认是允许匿名登录及无密码登录,这是非常不安全的,因此,我们还需要进行安全初始化

[root@Centos6 ~]# /usr/local/mysql/bin/mysql_secure_installation
NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!
In order to log into MariaDB to secure it, we'll need the current
password for the root user.  If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.
Enter current password for root (enter for none): 
OK, successfully used password, moving on...
Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.
Set root password? [Y/n] y                  
New password:                   <-- 设置root密码
Re-enter new password: 
Password updated successfully!
Reloading privilege tables..
 ... Success!
By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.
Remove anonymous users? [Y/n] y               <-- 禁止匿名登录
 ... Success!
Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.
Disallow root login remotely? [Y/n] y        <-- 禁止root远程登录
 ... Success!
By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.
Remove test database and access to it? [Y/n] 
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!
Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.
Reload privilege tables now? [Y/n] y        <-- 生效权限
 ... Success!
Cleaning up...
All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.
Thanks for using MariaDB!
[root@Centos6 ~]# mysql                 <-- 无密码登录已经禁止
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
[root@Centos6 ~]# mysql -uroot -p           <-- 正确登入
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or g.
Your MariaDB connection id is 17
Server version: 5.5.57-MariaDB MariaDB Server
Copyright (c) 2000, 2017, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
MariaDB [(none)]> use mysql
Database changed
MariaDB [mysql]> select user,host,password from user;
+------+-----------+-------------------------------------------+
| user | host      | password                                  |
+------+-----------+-------------------------------------------+
| root | localhost | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| root | 127.0.0.1 | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
| root | ::1       | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+------+-----------+-------------------------------------------+
3 rows in set (0.00 sec)

LVS负载均衡原理及安装配置详解

负载均衡集群是 load balance 集群的简写,翻译成中文就是负载均衡集群。常用的负载均衡开源软件有nginx、lvs、haproxy,商业的硬件负载均衡设备F5、Netscale。这里主要是学习 LVS 并对其进行了详细的总结记录。

一、负载均衡LVS基本介绍

LB集群的架构和原理很简单,就是当用户的请求过来时,会直接分发到Director Server上,然后它把用户的请求根据设置好的调度算法,智能均衡地分发到后端真正服务器(real server)上。为了避免不同机器上用户请求得到的数据不一样,需要用到了共享存储,这样保证所有用户请求的数据是一样的。

LVS是 Linux Virtual Server 的简称,也就是Linux虚拟服务器。这是一个由章文嵩博士发起的一个开源项目,它的官方网站是 http://www.linuxvirtualserver.org 现在 LVS 已经是 Linux 内核标准的一部分。使用 LVS 可以达到的技术目标是:通过 LVS 达到的负载均衡技术和 Linux 操作系统实现一个高性能高可用的 Linux 服务器集群,它具有良好的可靠性、可扩展性和可操作性。从而以低廉的成本实现最优的性能。LVS 是一个实现负载均衡集群的开源软件项目,LVS架构从逻辑上可分为调度层、Server集群层和共享存储。

二、LVS的基本工作原理

未分类

  1. 当用户向负载均衡调度器(Director Server)发起请求,调度器将请求发往至内核空间
  2. PREROUTING链首先会接收到用户请求,判断目标IP确定是本机IP,将数据包发往INPUT链
  3. IPVS是工作在INPUT链上的,当用户请求到达INPUT时,IPVS会将用户请求和自己已定义好的集群服务进行比对,如果用户请求的就是定义的集群服务,那么此时IPVS会强行修改数据包里的目标IP地址及端口,并将新的数据包发往POSTROUTING链
  4. POSTROUTING链接收数据包后发现目标IP地址刚好是自己的后端服务器,那么此时通过选路,将数据包最终发送给后端的服务器

三、LVS的组成

LVS 由2部分程序组成,包括 ipvs 和 ipvsadm。

  1. ipvs(ip virtual server):一段代码工作在内核空间,叫ipvs,是真正生效实现调度的代码。
  2. ipvsadm:另外一段是工作在用户空间,叫ipvsadm,负责为ipvs内核框架编写规则,定义谁是集群服务,而谁是后端真实的服务器(Real Server)

四、LVS相关术语

  1. DS:Director Server。指的是前端负载均衡器节点。
  2. RS:Real Server。后端真实的工作服务器。
  3. VIP:向外部直接面向用户请求,作为用户请求的目标的IP地址。
  4. DIP:Director Server IP,主要用于和内部主机通讯的IP地址。
  5. RIP:Real Server IP,后端服务器的IP地址。
  6. CIP:Client IP,访问客户端的IP地址。

下边是三种工作模式的原理和特点总结。

五、LVS/NAT原理和特点

1、重点理解NAT方式的实现原理和数据包的改变。

未分类

(a). 当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP
(b). PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链
(c). IPVS比对数据包请求的服务是否为集群服务,若是,修改数据包的目标IP地址为后端服务器IP,然后将数据包发至POSTROUTING链。 此时报文的源IP为CIP,目标IP为RIP
(d). POSTROUTING链通过选路,将数据包发送给Real Server
(e). Real Server比对发现目标为自己的IP,开始构建响应报文发回给Director Server。 此时报文的源IP为RIP,目标IP为CIP
(f). Director Server在响应客户端前,此时会将源IP地址修改为自己的VIP地址,然后响应给客户端。 此时报文的源IP为VIP,目标IP为CIP

2、LVS-NAT模型的特性

  • RS应该使用私有地址,RS的网关必须指向DIP
  • DIP和RIP必须在同一个网段内
  • 请求和响应报文都需要经过Director Server,高负载场景中,Director Server易成为性能瓶颈
  • 支持端口映射
  • RS可以使用任意操作系统
  • 缺陷:对Director Server压力会比较大,请求和响应都需经过director server

六、LVS/DR原理和特点

1、重将请求报文的目标MAC地址设定为挑选出的RS的MAC地址

未分类

(a) 当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP
(b) PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链
(c) IPVS比对数据包请求的服务是否为集群服务,若是,将请求报文中的源MAC地址修改为DIP的MAC地址,将目标MAC地址修改RIP的MAC地址,然后将数据包发至POSTROUTING链。 此时的源IP和目的IP均未修改,仅修改了源MAC地址为DIP的MAC地址,目标MAC地址为RIP的MAC地址
(d) 由于DS和RS在同一个网络中,所以是通过二层来传输。POSTROUTING链检查目标MAC地址为RIP的MAC地址,那么此时数据包将会发至Real Server。
(e) RS发现请求报文的MAC地址是自己的MAC地址,就接收此报文。处理完成之后,将响应报文通过lo接口传送给eth0网卡然后向外发出。 此时的源IP地址为VIP,目标IP为CIP
(f) 响应报文最终送达至客户端

2、LVS-DR模型的特性

  • 特点1:保证前端路由将目标地址为VIP报文统统发给Director Server,而不是RS
  • RS可以使用私有地址;也可以是公网地址,如果使用公网地址,此时可以通过互联网对RIP进行直接访问
  • RS跟Director Server必须在同一个物理网络中
  • 所有的请求报文经由Director Server,但响应报文必须不能进过Director Server
  • 不支持地址转换,也不支持端口映射
  • RS可以是大多数常见的操作系统
  • RS的网关绝不允许指向DIP(因为我们不允许他经过director)
  • RS上的lo接口配置VIP的IP地址
  • 缺陷:RS和DS必须在同一机房中

3、特点1的解决方案:

  • 在前端路由器做静态地址路由绑定,将对于VIP的地址仅路由到Director Server
  • 存在问题:用户未必有路由操作权限,因为有可能是运营商提供的,所以这个方法未必实用
  • arptables:在arp的层次上实现在ARP解析时做防火墙规则,过滤RS响应ARP请求。这是由iptables提供的
  • 修改RS上内核参数(arp_ignore和arp_announce)将RS上的VIP配置在lo接口的别名上,并限制其不能响应对VIP地址解析请求。

七、LVS/Tun原理和特点

在原有的IP报文外再次封装多一层IP首部,内部IP首部(源地址为CIP,目标IIP为VIP),外层IP首部(源地址为DIP,目标IP为RIP)

未分类

(a) 当用户请求到达Director Server,此时请求的数据报文会先到内核空间的PREROUTING链。 此时报文的源IP为CIP,目标IP为VIP 。
(b) PREROUTING检查发现数据包的目标IP是本机,将数据包送至INPUT链
(c) IPVS比对数据包请求的服务是否为集群服务,若是,在请求报文的首部再次封装一层IP报文,封装源IP为为DIP,目标IP为RIP。然后发至POSTROUTING链。 此时源IP为DIP,目标IP为RIP
(d) POSTROUTING链根据最新封装的IP报文,将数据包发至RS(因为在外层封装多了一层IP首部,所以可以理解为此时通过隧道传输)。 此时源IP为DIP,目标IP为RIP
(e) RS接收到报文后发现是自己的IP地址,就将报文接收下来,拆除掉最外层的IP后,会发现里面还有一层IP首部,而且目标是自己的lo接口VIP,那么此时RS开始处理此请求,处理完成之后,通过lo接口送给eth0网卡,然后向外传递。 此时的源IP地址为VIP,目标IP为CIP
(f) 响应报文最终送达至客户端

LVS-Tun模型特性

  • RIP、VIP、DIP全是公网地址
  • RS的网关不会也不可能指向DIP
  • 所有的请求报文经由Director Server,但响应报文必须不能进过Director Server
  • 不支持端口映射
  • RS的系统必须支持隧道

其实企业中最常用的是 DR 实现方式,而 NAT 配置上比较简单和方便,后边实践中会总结 DR 和 NAT 具体使用配置过程。

八、LVS的八种调度算法

  1. 轮叫调度 rr
    这种算法是最简单的,就是按依次循环的方式将请求调度到不同的服务器上,该算法最大的特点就是简单。轮询算法假设所有的服务器处理请求的能力都是一样的,调度器会将所有的请求平均分配给每个真实服务器,不管后端 RS 配置和处理能力,非常均衡地分发下去。

  2. 加权轮叫 wrr
    这种算法比 rr 的算法多了一个权重的概念,可以给 RS 设置权重,权重越高,那么分发的请求数越多,权重的取值范围 0 – 100。主要是对rr算法的一种优化和补充, LVS 会考虑每台服务器的性能,并给每台服务器添加要给权值,如果服务器A的权值为1,服务器B的权值为2,则调度到服务器B的请求会是服务器A的2倍。权值越高的服务器,处理的请求越多。

  3. 最少链接 lc
    这个算法会根据后端 RS 的连接数来决定把请求分发给谁,比如 RS1 连接数比 RS2 连接数少,那么请求就优先发给 RS1

  4. 加权最少链接 wlc
    这个算法比 lc 多了一个权重的概念。

  5. 基于局部性的最少连接调度算法 lblc
    这个算法是请求数据包的目标 IP 地址的一种调度算法,该算法先根据请求的目标 IP 地址寻找最近的该目标 IP 地址所有使用的服务器,如果这台服务器依然可用,并且有能力处理该请求,调度器会尽量选择相同的服务器,否则会继续选择其它可行的服务器

  6. 复杂的基于局部性最少的连接算法 lblcr
    记录的不是要给目标 IP 与一台服务器之间的连接记录,它会维护一个目标 IP 到一组服务器之间的映射关系,防止单点服务器负载过高。

  7. 目标地址散列调度算法 dh
    该算法是根据目标 IP 地址通过散列函数将目标 IP 与服务器建立映射关系,出现服务器不可用或负载过高的情况下,发往该目标 IP 的请求会固定发给该服务器。

  8. 源地址散列调度算法 sh
    与目标地址散列调度算法类似,但它是根据源地址散列算法进行静态分配固定的服务器资源。

九、实践LVS的NAT模式

1、实验环境

三台服务器,一台作为 director,两台作为 real server,director 有一个外网网卡(172.16.254.200) 和一个内网ip(192.168.0.8),两个 real server 上只有内网 ip (192.168.0.18) 和 (192.168.0.28),并且需要把两个 real server 的内网网关设置为 director 的内网 ip(192.168.0.8)

2、安装和配置

两个 real server 上都安装 nginx 服务
# yum install -y nginx

Director 上安装 ipvsadm
# yum install -y ipvsadm

Director 上编辑 nat 实现脚本

# vim /usr/local/sbin/lvs_nat.sh
# 编辑写入如下内容:
#! /bin/bash
# director服务器上开启路由转发功能:
echo 1 > /proc/sys/net/ipv4/ip_forward
# 关闭 icmp 的重定向
echo 0 > /proc/sys/net/ipv4/conf/all/send_redirects
echo 0 > /proc/sys/net/ipv4/conf/default/send_redirects
echo 0 > /proc/sys/net/ipv4/conf/eth0/send_redirects
echo 0 > /proc/sys/net/ipv4/conf/eth1/send_redirects
# director设置 nat 防火墙
iptables -t nat -F
iptables -t nat -X
iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j MASQUERADE
# director设置 ipvsadm
IPVSADM='/sbin/ipvsadm'
$IPVSADM -C
$IPVSADM -A -t 172.16.254.200:80 -s wrr
$IPVSADM -a -t 172.16.254.200:80 -r 192.168.0.18:80 -m -w 1
$IPVSADM -a -t 172.16.254.200:80 -r 192.168.0.28:80 -m -w 1

保存后,在 Director 上直接运行这个脚本就可以完成 lvs/nat 的配置

/bin/bash /usr/local/sbin/lvs_nat.sh

查看ipvsadm设置的规则

ipvsadm -ln

3、测试LVS的效果

通过浏览器测试2台机器上的web内容 http://172.16.254.200 。为了区分开,我们可以把 nginx 的默认页修改一下:

在 RS1 上执行
# echo "rs1rs1" >/usr/share/nginx/html/index.html

在 RS2 上执行
# echo "rs2rs2" >/usr/share/nginx/html/index.html

注意,切记一定要在两台 RS 上设置网关的 IP 为 director 的内网 IP。

十、实践LVS的DR模式

1、实验环境

三台机器:

  • Director节点: (eth0 192.168.0.8 vip eth0:0 192.168.0.38)
  • Real server1: (eth0 192.168.0.18 vip lo:0 192.168.0.38)
  • Real server2: (eth0 192.168.0.28 vip lo:0 192.168.0.38)

2、安装

两个 real server 上都安装 nginx 服务
# yum install -y nginx

Director 上安装 ipvsadm
# yum install -y ipvsadm

3、Director 上配置脚本

# vim /usr/local/sbin/lvs_dr.sh
#! /bin/bash
echo 1 > /proc/sys/net/ipv4/ip_forward
ipv=/sbin/ipvsadm
vip=192.168.0.38
rs1=192.168.0.18
rs2=192.168.0.28
ifconfig eth0:0 down
ifconfig eth0:0 $vip broadcast $vip netmask 255.255.255.255 up
route add -host $vip dev eth0:0
$ipv -C
$ipv -A -t $vip:80 -s wrr 
$ipv -a -t $vip:80 -r $rs1:80 -g -w 3
$ipv -a -t $vip:80 -r $rs2:80 -g -w 1

执行脚本:

# bash /usr/local/sbin/lvs_dr.sh

4、在2台 rs 上配置脚本:

# vim /usr/local/sbin/lvs_dr_rs.sh
#! /bin/bash
vip=192.168.0.38
ifconfig lo:0 $vip broadcast $vip netmask 255.255.255.255 up
route add -host $vip lo:0
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
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

rs 上分别执行脚本:

bash /usr/local/sbin/lvs_dr_rs.sh

5、实验测试

测试方式同上,浏览器访问 http://192.168.0.38

注意:在 DR 模式下,2台 rs 节点的 gateway 不需要设置成 dir 节点的 IP 。

参考链接地址:http://www.cnblogs.com/lgfeng/archive/2012/10/16/2726308.html

十一、LVS结合keepalive

LVS可以实现负载均衡,但是不能够进行健康检查,比如一个rs出现故障,LVS 仍然会把请求转发给故障的rs服务器,这样就会导致请求的无效性。keepalive 软件可以进行健康检查,而且能同时实现 LVS 的高可用性,解决 LVS 单点故障的问题,其实 keepalive 就是为 LVS 而生的。

1、实验环境

4台节点

  • Keepalived1 + lvs1(Director1):192.168.0.48
  • Keepalived2 + lvs2(Director2):192.168.0.58
  • Real server1:192.168.0.18
  • Real server2:192.168.0.28
  • IP: 192.168.0.38

2、安装系统软件

Lvs + keepalived的2个节点安装

# yum install ipvsadm keepalived -y

Real server + nginx服务的2个节点安装

# yum install epel-release -y
# yum install nginx -y

3、设置配置脚本

Real server节点2台配置脚本:

# vim /usr/local/sbin/lvs_dr_rs.sh
#! /bin/bash
vip=192.168.0.38
ifconfig lo:0 $vip broadcast $vip netmask 255.255.255.255 up
route add -host $vip lo:0
echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
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

2节点rs 上分别执行脚本:
bash /usr/local/sbin/lvs_dr_rs.sh

keepalived节点配置(2节点):

主节点( MASTER )配置文件
vim /etc/keepalived/keepalived.conf
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.0.38
    }
}

virtual_server 192.168.0.38 80 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    persistence_timeout 0
    protocol TCP

    real_server 192.168.0.18 80 {
        weight 1
        TCP_CHECK {
            connect_timeout 10
            nb_get_retry 3
            delay_before_retry 3
            connect_port 80
        }
    }

    real_server 192.168.0.28 80 {
        weight 1
        TCP_CHECK {
            connect_timeout 10
            nb_get_retry 3
            delay_before_retry 3
            connect_port 80
        }
    }
}

从节点( BACKUP )配置文件

拷贝主节点的配置文件keepalived.conf,然后修改如下内容:

state MASTER -> state BACKUP
priority 100 -> priority 90

keepalived的2个节点执行如下命令,开启转发功能:

# echo 1 > /proc/sys/net/ipv4/ip_forward

4、启动keepalive

先主后从分别启动keepalive
service keepalived start

5、验证结果

实验1

手动关闭192.168.0.18节点的nginx,service nginx stop 在客户端上去测试访问 http://192.168.0.38 结果正常,不会出现访问18节点,一直访问的是28节点的内容。

实验2

手动重新开启 192.168.0.18 节点的nginx, service nginx start 在客户端上去测试访问 http://192.168.0.38 结果正常,按照 rr 调度算法访问18节点和28节点。

实验3

测试 keepalived 的HA特性,首先在master上执行命令 ip addr ,可以看到38的vip在master节点上的;这时如果在master上执行 service keepalived stop 命令,这时vip已经不再master上,在slave节点上执行 ip addr 命令可以看到 vip 已经正确漂到slave节点,这时客户端去访问 http://192.168.0.38 访问依然正常,验证了 keepalived的HA特性。

配置LVS keepalived主从切换时同步连接状态信息

由于LVS负载均衡器需要保存大量的连接信息,记录每个TCP连接由哪台真实服务器处理。

[root@localhost keepshell]# ipvsadm -L -n -c
IPVS connection entries
pro expire state       source             virtual            destination
TCP 14:58  ESTABLISHED 192.168.80.1:57622 192.168.80.138:8080 192.168.80.135:8080
TCP 14:58  ESTABLISHED 192.168.80.1:57624 192.168.80.138:8080 192.168.80.135:8080
TCP 14:58  ESTABLISHED 192.168.80.1:57621 192.168.80.138:8080 192.168.80.136:8080
TCP 14:58  ESTABLISHED 192.168.80.1:57625 192.168.80.138:8080 192.168.80.136:8080
TCP 14:58  ESTABLISHED 192.168.80.1:57623 192.168.80.138:8080 192.168.80.136:8080
TCP 14:58  ESTABLISHED 192.168.80.1:57626 192.168.80.138:8080 192.168.80.135:8080

当主负载均衡器宕机以后,备机提升为主,但备机缺省没有这些连接信息,会导致客户端的连接失效,为了解决这一问题,LVS在Linux内核实现了同步连接信息的功能.

ipvsadm -L --daemon  查看同步进程信息

ipvsadm --start-daemon master|backup --mcast-interface=网卡名称 --syncid 编号,主备需要一致

ipvsadm --stop-daemon master|backup 停止同步
ipvsadm -L -n -c 查看连接状态信息

下面给出配置

keepalived.conf

! Configuration File for keepalived  

global_defs {  
   notification_email {  
     root@localhost  
   }  
   notification_email_from root@localhost    
   smtp_server localhost    
   smtp_connect_timeout 30    
   router_id  NodeA  
} 


! Configuration File for keepalived  

global_defs {  
   notification_email {  
     root@localhost  
   }  
   notification_email_from root@localhost    
   smtp_server localhost    
   smtp_connect_timeout 30    
   router_id  NodeA  
} 

virtual_server 192.168.80.138 8080 {   
   delay_loop 6                    
   lb_algo rr                       
   lb_kind DR                            
   persistence_timeout 0             
   protocol TCP                      
   real_server 192.168.80.135 8080 {     
       weight 1                        
        HTTP_GET {  
            url {   
              path /index.jsp  
              digest 5cce221db9752be2116860efb866144e  
            }  
            connect_timeout 5  
            nb_get_retry 3  
            delay_before_retry 3  
        }         
   }  

   real_server 192.168.80.136 8080 {      
       weight 1  
        HTTP_GET {  
            url {   
              path /index.jsp  
              digest 345e829e9a900a87a8fce740ef243198  
            }  
            connect_timeout 5  
            nb_get_retry 3  
            delay_before_retry 3  
        }          
   }  
}  

notify_master.sh

#!/bin/bash  
echo $(date "+%Y-%m-%d %H:%M:%S") "The keepalived service is master." >> /home/keepshell/gexin.txt  
ipvsadm --stop-daemon backup  
ipvsadm --start-daemon master --mcast-interface=eno16777736 --syncid 1 

notify_backup.sh

#!/bin/bash  
echo $(date "+%Y-%m-%d %H:%M:%S") "The keepalived service is backup." >> /home/keepshell/gexin.txt  
ipvsadm --stop-daemon master  
ipvsadm --start-daemon backup --mcast-interface=eno16777736 --syncid 1  

notify_stop.sh

#!/bin/bash  
echo $(date "+%Y-%m-%d %H:%M:%S") "The keepalived service is stop." >> /home/keepshell/gexin.txt  
ipvsadm --stop-daemon master  
ipvsadm --stop-daemon backup 

对CentOS-7使用xfs文件系统的LVM进行扩容

Xfs是CentOS7的默认文件系统类型,而不同文件系统类型对应的创建、检查、调整命令不同。

未分类

在xfs文件系统中,只能增大分区而不能减小。

[root@localhost ~]# ls /lib//modules/3.10.0-229.20.1.el7.x86_64/kernel/fs   #查看内核所支持的所有文件系统类型
binfmt_misc.ko  ceph    dlm    fat      gfs2   lockd       nfs_common  overlayfs  udf
btrfs           cifs    exofs  fscache  isofs  mbcache.ko  nfsd        pstore     xfs
cachefiles      cramfs  ext4   fuse     jbd2   nfs         nls         squashfs

我之前已经新建了分区并加入到vg中,对物理边界也已扩展。

扩展逻辑边界时,报错如下所示:

[root@localhost ~]# resize2fs -p /dev/mapper/centos-root     
resize2fs 1.42.9 (28-Dec-2013)
resize2fs: Bad magic number in super-block 当尝试打开 /dev/mapper/centos-root 时
找不到有效的文件系统超级块.

首先想到使用fsck进行修复下,并没有用,看到错误信息,才知道xfs文件要使用xfs_repair进行修复

[root@localhost ~]# fsck /dev/mapper/centos-root      
fsck,来自 util-linux 2.23.2
If you wish to check the consistency of an XFS filesystem or
repair a damaged filesystem, see xfs_repair(8).

然后尝试修复下,然而并没有用,需要卸载才能进行修复,而此文件系统是挂载到 / 下的,所以想都别想。

[root@localhost ~]# xfs_repair /dev/mapper/centos-root 
xfs_repair: /dev/mapper/centos-root contains a mounted filesystem
xfs_repair: /dev/mapper/centos-root contains a mounted and writable filesystem

fatal error -- couldn't initialize XFS library

最后在网上查了一番后,才知道xfs文件系统在进行逻辑扩展后,还要进行一步才能完成:

[root@localhost ~]# lvs
  LV   VG     Attr       LSize  Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
  root centos -wi-ao---- 95.00g                                                    
  swap centos -wi-ao----  3.88g             
[root@localhost ~]# df  -lh
文件系统                 容量  已用  可用 已用% 挂载点
/dev/mapper/centos-root   46G   42G  4.5G   91% /   ------------>46G
devtmpfs                 1.9G     0  1.9G    0% /dev
tmpfs                    1.9G  164K  1.9G    1% /dev/shm
tmpfs                    1.9G  8.7M  1.9G    1% /run
tmpfs                    1.9G     0  1.9G    0% /sys/fs/cgroup
/dev/sda1                497M  208M  290M   42% /boot                                       
[root@localhost ~]# xfs_growfs /dev/mapper/centos-root      #执行调整,扩展后需要执行此步骤
meta-data=/dev/mapper/centos-root isize=256    agcount=4, agsize=2987776 blks
         =                       sectsz=512   attr=2, projid32bit=1
         =                       crc=0        finobt=0
data     =                       bsize=4096   blocks=11951104, imaxpct=25
         =                       sunit=0      swidth=0 blks
naming   =version 2              bsize=4096   ascii-ci=0 ftype=0
log      =internal               bsize=4096   blocks=5835, version=2
         =                       sectsz=512   sunit=0 blks, lazy-count=1
realtime =none                   extsz=4096   blocks=0, rtextents=0
data blocks changed from 11951104 to 24903680

[root@localhost ~]# df -lh
文件系统                 容量  已用  可用 已用% 挂载点
/dev/mapper/centos-root   95G   42G   54G   44% /      ------------>已经完成扩展
devtmpfs                 1.9G     0  1.9G    0% /dev
tmpfs                    1.9G  164K  1.9G    1% /dev/shm
tmpfs                    1.9G  8.7M  1.9G    1% /run
tmpfs                    1.9G     0  1.9G    0% /sys/fs/cgroup
/dev/sda1                497M  208M  290M   42% /boot

xfs相关常用命令

xfs_admin: 调整 xfs 文件系统的各种参数  
xfs_copy: 拷贝 xfs 文件系统的内容到一个或多个目标系统(并行方式)  
xfs_db: 调试或检测 xfs 文件系统(查看文件系统碎片等)  
xfs_check: 检测 xfs 文件系统的完整性  
xfs_bmap: 查看一个文件的块映射  
xfs_repair: 尝试修复受损的 xfs 文件系统  
xfs_fsr: 碎片整理  
xfs_quota: 管理 xfs 文件系统的磁盘配额  
xfs_metadump: 将 xfs 文件系统的元数据 (metadata) 拷贝到一个文件中  
xfs_mdrestore: 从一个文件中将元数据 (metadata) 恢复到 xfs 文件系统  
xfs_growfs: 调整一个 xfs 文件系统大小(只能扩展)  
xfs_freeze    暂停(-f)和恢复(-u)xfs 文件系统
xfs_logprint: 打印xfs文件系统的日志  
xfs_mkfile: 创建xfs文件系统  
xfs_info: 查询文件系统详细信息  
xfs_ncheck: generate pathnames from i-numbers for XFS  
xfs_rtcp: XFS实时拷贝命令   
xfs_io: 调试xfs I/O路径

iptables防火墙规则的添加、删除、修改、保存

本文介绍iptables这个Linux下最强大的防火墙工具,包括配置iptables三个链条的默认规则、添加iptables规则、修改规则、删除规则等。

一、查看规则集

iptables --list -n // 加一个-n以数字形式显示IP和端口,看起来更舒服

二、配置默认规则

iptables -P INPUT DROP  // 不允许进
iptables -P FORWARD DROP  // 不允许转发
iptables -P OUTPUT ACCEPT  // 允许出

三、增加规则

iptables -A INPUT -s 192.168.0.0/24 -j ACCEPT
//允许源IP地址为192.168.0.0/24网段的包流进(包括所有的协议,这里也可以指定单个IP)
iptables -A INPUT -d 192.168.0.22 -j ACCEPT
//允许所有的IP到192.168.0.22的访问
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
//开放本机80端口
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
//开放本机的ICMP协议

四、删除规则

iptables -D INPUT -s 192.168.0.21 -j ACCEPT
//删除刚才建立的第一条规则

五、规则的保存

iptables -F
//清空规则缓冲区(这个操作会将上面的增加操作全部清空,若须保留建议先执行一下句:保存)
service iptables save
//将规则保存在/etc/sysconfig/iptables文件里
service iptables restart
//重启Iptables服务

最后说明一下,iptables防火墙的配置文件存放于:/etc/sysconfig/iptables

Ubuntu 16.04安装Docker Compose及简单的使用示例

什么是 Docker Compose

Docker Compose 是一个运行多容器 Docker 应用的工具。Compose 通过一个配置文件来配置一个应用的服务,然后通过一个命令创建并启动所有在配置文件中指定的服务。

Docker Compose 适用于许多不同的项目,如:

  • 开发:利用 Compose 命令行工具,我们可以创建一个隔离(而可交互)的环境来承载正在开发中的应用程序。通过使用 Compose 文件,开发者可以记录和配置所有应用程序的服务依赖关系。
  • 自动测试:此用例需求一个测试运行环境。Compose 提供了一种方便的方式来管理测试套件的隔离测试环境。完整的环境在 Compose 文件中定义。

Docker Compose 是在 Fig 的源码上构建的,这个社区项目现在已经没有使用了。

在本教程中,我们将看到如何在 Ubuntn 16.04 上安装 Docker Compose。

安装 Docker

我们需要安装 Docker 来安装 Docker Compose。首先为官方 Docker 仓库添加公钥。

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

接下来,添加 Docker 仓库到 apt 源列表:

$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

更新包数据库,并使用 apt 安装 Docker

$ sudo apt-get update
$ sudo apt install docker-ce

在安装进程结束后,Docker 守护程序应该已经启动并设为开机自动启动。我们可以通过下面的命令来查看它的状态:

$ sudo systemctl status docker
---------------------------------
● docker.service - Docker Application Container Engine
 Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
 Active: active (running) 

安装 Docker Compose

现在可以安装 Docker Compose 了。通过执行以下命令下载当前版本。

# curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

为二进制文件添加执行权限:

# chmod +x /usr/local/bin/docker-compose

检查 Docker Compose 版本:

$ docker-compose -v

输出应该如下:

docker-compose version 1.14.0, build c7bdf9e

测试 Docker Compose

Docker Hub 包含了一个用于演示的 Hello World 镜像,可以用来说明使用 Docker Compose 来运行一个容器所需的配置。

创建并打开一个目录:

$ mkdir hello-world
$ cd hello-world

创建一个新的 YAML 文件:

$ $EDITOR docker-compose.yml

在文件中粘贴如下内容:

unixmen-compose-test:
 image: hello-world

注意: 第一行是容器名称的一部分。

保存并退出。

运行容器

接下来,在 hello-world 目录执行以下命令:

$ sudo docker-compose up

如果一切正常,Compose 输出应该如下:

Pulling unixmen-compose-test (hello-world:latest)...
latest: Pulling from library/hello-world
b04784fba78d: Pull complete
Digest: sha256:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f
Status: Downloaded newer image for hello-world:latest
Creating helloworld_unixmen-compose-test_1 ... 
Creating helloworld_unixmen-compose-test_1 ... done
Attaching to helloworld_unixmen-compose-test_1
unixmen-compose-test_1 | 
unixmen-compose-test_1 | Hello from Docker!
unixmen-compose-test_1 | This message shows that your installation appears to be working correctly.
unixmen-compose-test_1 | 
unixmen-compose-test_1 | To generate this message, Docker took the following steps:
unixmen-compose-test_1 | 1. The Docker client contacted the Docker daemon.
unixmen-compose-test_1 | 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
unixmen-compose-test_1 | 3. The Docker daemon created a new container from that image which runs the
unixmen-compose-test_1 | executable that produces the output you are currently reading.
unixmen-compose-test_1 | 4. The Docker daemon streamed that output to the Docker client, which sent it
unixmen-compose-test_1 | to your terminal.
unixmen-compose-test_1 | 
unixmen-compose-test_1 | To try something more ambitious, you can run an Ubuntu container with:
unixmen-compose-test_1 | $ docker run -it ubuntu bash
unixmen-compose-test_1 | 
unixmen-compose-test_1 | Share images, automate workflows, and more with a free Docker ID:
unixmen-compose-test_1 | https://cloud.docker.com/
unixmen-compose-test_1 | 
unixmen-compose-test_1 | For more examples and ideas, visit:
unixmen-compose-test_1 | https://docs.docker.com/engine/userguide/
unixmen-compose-test_1 | 
helloworld_unixmen-compose-test_1 exited with code 0

Docker 容器只能在命令(LCTT 译注:此处应为容器中的命令)还处于活动状态时运行,因此当测试完成运行时,容器将停止运行。

结论

本文是关于在 Ubuntu 16.04 中安装 Docker Compose 的教程。我们还看到了如何通过一个 YAML 格式的 Compose 文件构建一个简单的项目。

用iptables关闭docker映射到host上的端口

未分类

docker可以让我们很方便地安装本地服务。但同时,默认的docker的设置使这些端口可以很轻松地从remote访问。
理想的做法是利用nginx把docker 默认打开的端口反向代理到别的端口,然后对新的端口进行用户验证保护。

屏蔽外部端口访问我们很自然而然就想到了使用iptables。

目的

我们在运行某个container的时候,使用了端口映射,例如

$ docker docker run --name myservice -p 5000:5000 some_image_name

我们想要仅允许在服务器上通过localhost:5000访问docker container的服务,而屏蔽掉外部通过myhostname:5000来访问服务的request。

错误的尝试

$ sudo iptables -A INPUT -p tcp -m tcp --dport 5000 -j ACCEPT

尝试在浏览器输入myhostname:5000,发现依然可以访问到5000端口。

这个是为什么呢?

选择正确的chain

第一次通过简单搜索谷歌,复制粘贴的方法失败了。看来还得静下来看一下iptables这个东西。

这篇文章https://securitynik.blogspot.jp/2016/12/docker-networking-internals-how-docker_16.html

很好解释了docker中iptables的用法。

然而iptables的概念太多。我们把范围缩小到filter这个table中。

简要来说,我们通常需要处理的是三条chain(INPUT, FORWARD, OUTPUT)。所有的package 通过相应的chain,chain中的规则进行match。如果有match的规则,则执行规则规定的动作,否则继续向后访问规则,最后最后如果没有匹配的规则,则使用chain的默认的policy来处理。 默认的Policy可以是 ACCEPT 或者 DROP。分别表示接受和丢弃该Package。 被Drop的表现通常是,在浏览器上,显示load中但是总是无法出来结果。

当我们启动docker deamon,挂上docker container的时候,docker会在FORWARD chain中追加叫DOCKER和DOCKER-ISOLATION的自定义CHAIN。

由此可见。我们要追加的规则应该追加在FORWARD chain而不是在INPUT chain。

正确的做法是

$ sudo iptables -I FORWARD -p tcp -m tcp --dport 5000 -j ACCEPT

但是注意,docker每次重启之后都会把自定义的DOCKER chain 插到FORWARD chain的第一个。所以,我们不如把这个规则写到DOCKER chain中。

$ sudo iptables -I DOCKER -p tcp -m tcp --dport 5000 -j ACCEPT

其他疑惑

  • 为什么iptables知道特定的外部reqeust需要走的是FORWARD而不是INPUT的chain?

这个是因为在filter table之前由NAT table替换了走向docker的request的destination。并不是所有到本机的请求都是走INPUT chain的,想象一下如果本机是NAT的gateway,那么很显然,大部分package需要转发到下面去。

  • 为什么经常有两条一摸一样的规则?

光用iptables -L的话会看到几乎完全一样的两行,我们查看具体内容需要iptables -v,这样可以看到in out两个参数,这两个参数分别为in的网卡端口,和out的网卡端口。

CentOS 7.3 Docker私有仓库Harbor安装部署

1、部署环境

  • Centos7.3 x64
  • docker-ce-17.06.0
  • docker-compose-1.15.0
  • Python-2.7.5(系统默认)

2、Docker及Docker-compose安装

 yum install -y yum-utils device-mapper-persistent-data lvm2
 yum-config-manager 
    --add-repo 
    https://download.docker.com/linux/centos/docker-ce.repo
 yum-config-manager --enable docker-ce-edge
 yum makecache fast
 systemctl start docker 
 systemctl enable docker

curl -L https://github.com/docker/compose/releases/download/1.15.0/docker-compose-`uname -s`-`uname -m` &gt; /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

3、Habor部署配置

wget https://github.com/vmware/harbor/releases/download/v1.1.2/harbor-offline-installer-v1.1.2.tgz
tar xf harbor-offline-installer-v1.1.2.tgz
cd harbor/

vim harbor.cfg
hostname = hub.wow
其他默认(http协议)

./install.sh
安装成功后,可以通过http://hub.wow/访问

未分类

4、Docker客户端使用

由于Harbor默认使用的http协议,故需要在Docker client上的Dockerd服务增加–insecure-registry hub.wow
Centos7修改方式为:

vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd  --insecure-registry hub.wow

systemctl daemon-reload
systemctl reload docker
[root@localhost harbor]# docker login -u admin -p Harbor12345 hub.wow
官方仓库下载busybox镜像
[root@localhost harbor]# docker pull busybox 
[root@localhost harbor]# docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
busybox                     latest              efe10ee6727f        2 weeks ago         1.13MB
本地基于busybox:latest创建标记hub.wow/busybox:latest
[root@localhost harbor]# docker tag busybox:latest hub.wow/project_name/busybox:latest
推送本地镜像busybox:latest 到hub.wow私有仓库
[root@localhost harbor]# docker push hub.wow/project_name/busybox:latest

5、Harbor服务管理

cd harbor/
docker-compose -f ./docker-compose.yml [ up|down|ps|stop|start ]

Docker Registry的安装及镜像管理方法

Why Docker Registry?

有时我们的服务器无法访问互联网,或者你不希望将自己的镜像放到公网当中,那么你就需要Docker Registry,它可以用来存储和管理自己的镜像。

How to install Docker Registry?

其实创建私有的Docker仓库非常简单,只需要运行一个Registry容器即可,该容器存储在Docker HUB中。

docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry
  • -d 是后台启动容器。

  • -p 将容器的 5000 端口映射到 Host 的 5000 端口。5000 是 registry 服务端口。

  • -v 将容器 /var/lib/registry目录映射到宿主机的/myregistry,用于存放镜像数据。

How to use Docker Registry?

理论上我们已经搭建好了自己的私有镜像仓库,但这时候还有一些问题需要我们解决。

在pull或者push镜像时报拒绝连接的错误。

这是由于Registry为了安全性考虑,默认是需要证书支持的,证书这块我倒没有研究过,可以通过一个简单的办法解决。创建或者修改/etc/docker/daemon.json文件,并在其中写入

{
    "insecure-registries": ["<ip>:5000"] 
}

其中为安装了Registry的机器ip地址。需要注意的是在安装registry的节点和客户端需要访问私有Registry的节点都需要执行此步操作。

修改文之后执行以下命令重启节点docker。

systemctl daemon-reload
systemctl restart docker

我是在ubuntu16环境进行的上述操作,其他系统可以参考相应命令。

通过 docker tag重命名镜像,使之与registry匹配。

docker tag wsf <ip>:5000/wsf:v1

给wsf镜像重命名为:5000/wsf:v1,v1为版本号,前面必须要加上域名或IP地址(运行上面registry容器的地址)和端口号。

其实Docker HUB与我们要建立的私有Registry没有本质的区别。

docker run ubuntu 

语句从官方hub中寻找镜像,它是

docker run docker.io/library/ubuntu 

的简写。

docker.io即是上面的ip地址,端口号为80省略。

上传镜像

docker push <ip>:5000/wsf:v1

下载镜像

docker pull <ip>:5000/wsf:v1

上传和下载镜像都可以在任意能访问Registry的节点上进行。前提是此节点必须进行上述的修改daemon.json操作。

查看Registry中所有镜像信息

curl http://<ip>:5000/v2/_catalog

此语句会返回一个json,包含当前Registry中存储的镜像信息。

总结

本文简单为大家介绍了Docker Registry的相关细节,有了这个东西就能更好的实现docker的多主机管理和我们自定义镜像的管理。

配置docker限制容器对cpu 内存和IO的资源使用

在使用 docker 运行容器时,一台主机上可能会运行几百个容器,这些容器虽然互相隔离,但是底层却使用着相同的 CPU、内存和磁盘资源。如果不对容器使用的资源进行限制,那么容器之间会互相影响,小的来说会导致容器资源使用不公平;大的来说,可能会导致主机和集群资源耗尽,服务完全不可用。

docker 作为容器的管理者,自然提供了控制容器资源的功能。正如使用内核的 namespace 来做容器之间的隔离,docker 也是通过内核的 cgroups 来做容器的资源限制。这篇文章就介绍如何使用 docker 来限制 CPU、内存和 IO,以及对应的 cgroups 文件。

NOTE:如果想要了解 cgroups 的更多信息,可以参考 kernel 文档 或者其他资源。

我本地测试的 docker 版本是 17.03.0 社区版:

➜  stress docker version
Client:
 Version:      17.03.0-ce
 API version:  1.26
 Go version:   go1.7.5
 Git commit:   60ccb22
 Built:        Thu Feb 23 11:02:43 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.03.0-ce
 API version:  1.26 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   60ccb22
 Built:        Thu Feb 23 11:02:43 2017
 OS/Arch:      linux/amd64
 Experimental: false

使用的是 ubuntu 16.04 系统,内核版本是 4.10.0:

➜  ~ uname -a
Linux cizixs-ThinkPad-T450 4.10.0-28-generic #32~16.04.2-Ubuntu SMP Thu Jul 20 10:19:48 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

NOTE: 不同版本和系统的功能会有差异,具体的使用方法和功能解释请以具体版本的 docker 官方文档为准。

我们使用 stress 容器来产生 CPU、内存和 IO 的压力,具体的使用请参考它的帮助文档。

1、CPU 资源

主机上的进程会通过时间分片机制使用 CPU,CPU 的量化单位是频率,也就是每秒钟能执行的运算次数。为容器限制 CPU 资源并不能改变 CPU 的运行频率,而是改变每个容器能使用的 CPU 时间片。理想状态下,CPU 应该一直处于运算状态(并且进程需要的计算量不会超过 CPU 的处理能力)。

docker 限制 CPU Share

docker 允许用户为每个容器设置一个数字,代表容器的 CPU share,默认情况下每个容器的 share 是 1024。要注意,这个 share 是相对的,本身并不能代表任何确定的意义。当主机上有多个容器运行时,每个容器占用的 CPU 时间比例为它的 share 在总额中的比例。举个例子,如果主机上有两个一直使用 CPU 的容器(为了简化理解,不考虑主机上其他进程),其 CPU share 都是 1024,那么两个容器 CPU 使用率都是 50%;如果把其中一个容器的 share 设置为 512,那么两者 CPU 的使用率分别为 67% 和 33%;如果删除 share 为 1024 的容器,剩下来容器的 CPU 使用率将会是 100%。

总结下来,这种情况下,docker 会根据主机上运行的容器和进程动态调整每个容器使用 CPU 的时间比例。这样的好处是能保证 CPU 尽可能处于运行状态,充分利用 CPU 资源,而且保证所有容器的相对公平;缺点是无法指定容器使用 CPU 的确定值。

docker 为容器设置 CPU share 的参数是 -c –cpu-shares,它的值是一个整数。

我的机器是 4 核 CPU,因此使用 stress 启动 4 个进程来产生计算压力:

➜  stress docker run --rm -it stress --cpu 4
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 12000us
stress: dbug: [1] --> hogcpu worker 4 [7] forked
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [8] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [9] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [10] forked

在另外一个 terminal 使用 htop 查看资源的使用情况:

未分类

从上图中可以看到,CPU 四个核资源都达到了 100%。四个 stress 进程 CPU 使用率没有达到 100% 是因为系统中还有其他机器在运行。

为了比较,我另外启动一个 share 为 512 的容器:

➜  stress docker run --rm -it -c 512 stress --cpu 4 
stress: info: [1] dispatching hogs: 4 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 12000us
stress: dbug: [1] --> hogcpu worker 4 [6] forked
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [7] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [8] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [9] forked

因为默认情况下,容器的 CPU share 为 1024,所以这两个容器的 CPU 使用率应该大致为 2:1,下面是启动第二个容器之后的监控截图:

未分类

两个容器分别启动了四个 stress 进程,第一个容器 stress 进程 CPU 使用率都在 54% 左右,第二个容器 stress 进程 CPU 使用率在 25% 左右,比例关系大致为 2:1,符合之前的预期。

限制容器能使用的 CPU 核数

上面讲述的 -c –cpu-shares 参数只能限制容器使用 CPU 的比例,或者说优先级,无法确定地限制容器使用 CPU 的具体核数;从 1.13 版本之后,docker 提供了 –cpus 参数可以限定容器能使用的 CPU 核数。这个功能可以让我们更精确地设置容器 CPU 使用量,是一种更容易理解也因此更常用的手段。

–cpus 后面跟着一个浮点数,代表容器最多使用的核数,可以精确到小数点二位,也就是说容器最小可以使用 0.01 核 CPU。比如,我们可以限制容器只能使用 1.5 核数 CPU:

➜  ~ docker run --rm -it --cpus 1.5 stress --cpu 3
stress: info: [1] dispatching hogs: 3 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 9000us
stress: dbug: [1] --> hogcpu worker 3 [7] forked
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [8] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [9] forked

在容器里启动三个 stress 来跑 CPU 压力,如果不加限制,这个容器会导致 CPU 的使用率为 300% 左右(也就是说会占用三个核的计算能力)。实际的监控如下图:

未分类

可以看到,每个 stress 进程 CPU 使用率大约在 50%,总共的使用率为 150%,符合 1.5 核的设置。

如果设置的 –cpus 值大于主机的 CPU 核数,docker 会直接报错:

➜  ~ docker run --rm -it --cpus 8 stress --cpu 3
docker: Error response from daemon: Range of CPUs is from 0.01 to 4.00, as there are only 4 CPUs available.
See 'docker run --help'.

如果多个容器都设置了 –cpus ,并且它们之和超过主机的 CPU 核数,并不会导致容器失败或者退出,这些容器之间会竞争使用 CPU,具体分配的 CPU 数量取决于主机运行情况和容器的 CPU share 值。也就是说 –cpus 只能保证在 CPU 资源充足的情况下容器最多能使用的 CPU 数,docker 并不能保证在任何情况下容器都能使用这么多的 CPU(因为这根本是不可能的)。

限制容器运行在某些 CPU 核

现在的笔记本和服务器都会有多个 CPU,docker 也允许调度的时候限定容器运行在哪个 CPU 上。比如,我的主机上有 4 个核,可以通过 –cpuset 参数让容器只运行在前两个核上:

➜  ~ docker run --rm -it --cpuset-cpus=0,1 stress --cpu 2
stress: info: [1] dispatching hogs: 2 cpu, 0 io, 0 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 6000us
stress: dbug: [1] --> hogcpu worker 2 [7] forked
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogcpu worker 1 [8] forked

这样,监控中可以看到只有前面两个核 CPU 达到了 100% 使用率。

未分类

–cpuset-cpus 参数可以和 -c –cpu-shares 一起使用,限制容器只能运行在某些 CPU 核上,并且配置了使用率。

限制容器运行在哪些核上并不是一个很好的做法,因为它需要实现知道主机上有多少 CPU 核,而且非常不灵活。除非有特别的需求,一般并不推荐在生产中这样使用。

CPU 信息的 cgroup 文件

所有和容器 CPU share 有关的配置都在 /sys/fs/cgroup/cpu/docker// 目录下面,其中 cpu.shares 保存了 CPU share 的值(其他文件的意义可以查看 cgroups 的官方文档):

➜  ~ ls /sys/fs/cgroup/cpu/docker/d93c9a660f4a13789d995d56024f160e2267f2dc26ce676daa66ea6435473f6f/   
cgroup.clone_children  cpuacct.stat   cpuacct.usage_all     cpuacct.usage_percpu_sys   cpuacct.usage_sys   cpu.cfs_period_us  cpu.shares  notify_on_release
cgroup.procs           cpuacct.usage  cpuacct.usage_percpu  cpuacct.usage_percpu_user  cpuacct.usage_user  cpu.cfs_quota_us   cpu.stat    tasks
➜  ~ cat /sys/fs/cgroup/cpu/docker/d93c9a660f4a13789d995d56024f160e2267f2dc26ce676daa66ea6435473f6f/cpu.shares 
1024

和 cpuset(限制 CPU 核)有关的文件在 /sys/fs/cgroup/cpuset/docker/ 目录下,其中 cpuset.cpus 保存了当前容器能使用的 CPU 核:

➜  ~ ls /sys/fs/cgroup/cpuset/docker/d93c9a660f4a13789d995d56024f160e2267f2dc26ce676daa66ea6435473f6f/
cgroup.clone_children  cpuset.cpus            cpuset.mem_exclusive   cpuset.memory_pressure     cpuset.mems                      notify_on_release
cgroup.procs           cpuset.effective_cpus  cpuset.mem_hardwall    cpuset.memory_spread_page  cpuset.sched_load_balance        tasks
cpuset.cpu_exclusive   cpuset.effective_mems  cpuset.memory_migrate  cpuset.memory_spread_slab  cpuset.sched_relax_domain_level

➜  ~ cat /sys/fs/cgroup/cpuset/docker/d93c9a660f4a13789d995d56024f160e2267f2dc26ce676daa66ea6435473f6f/cpuset.cpus
0-1

–cpus 限制 CPU 核数并不像上面两个参数一样有对应的文件对应,它是由 cpu.cfs_period_us 和 cpu.cfs_quota_us 两个文件控制的。如果容器的 –cpus 设置为 3,其对应的这两个文件值为:

➜  ~ cat /sys/fs/cgroup/cpu/docker/233a38cc641f2e4a1bec3434d88744517a2214aff9d8297e908fa13b9aa12e02/cpu.cfs_period_us 
100000
➜  ~ cat /sys/fs/cgroup/cpu/docker/233a38cc641f2e4a1bec3434d88744517a2214aff9d8297e908fa13b9aa12e02/cpu.cfs_quota_us 
300000

其实在 1.12 以及之前的版本,都是通过 –cpu-period 和 –cpu-quota 这两个参数控制容器能使用的 CPU 核数的。前者表示 CPU 的周期数,默认是 100000,单位是微秒,也就是 1s,一般不需要修改;后者表示容器的在上述 CPU 周期里能使用的 quota,真正能使用的 CPU 核数就是 cpu-quota / cpu-period,因此对于 3 核的容器,对应的 cpu-quota 值为 300000。

2、内存资源

默认情况下,docker 并没有对容器内存进行限制,也就是说容器可以使用主机提供的所有内存。这当然是非常危险的事情,如果某个容器运行了恶意的内存消耗软件,或者代码有内存泄露,很可能会导致主机内存耗尽,因此导致服务不可用。对于这种情况,docker 会设置 docker daemon 的 OOM(out of memory) 值,使其在内存不足的时候被杀死的优先级降低。另外,就是你可以为每个容器设置内存使用的上限,一旦超过这个上限,容器会被杀死,而不是耗尽主机的内存。

限制内存上限虽然能保护主机,但是也可能会伤害到容器里的服务。如果为服务设置的内存上限太小,会导致服务还在正常工作的时候就被 OOM 杀死;如果设置的过大,会因为调度器算法浪费内存。因此,合理的做法包括:

  • 为应用做内存压力测试,理解正常业务需求下使用的内存情况,然后才能进入生产环境使用
  • 一定要限制容器的内存使用上限
  • 尽量保证主机的资源充足,一旦通过监控发现资源不足,就进行扩容或者对容器进行迁移
  • 如果可以(内存资源充足的情况),尽量不要使用 swap,swap 的使用会导致内存计算复杂,对调度器非常不友好

docker 限制容器内存使用量

在 docker 启动参数中,和内存限制有关的包括(参数的值一般是内存大小,也就是一个正数,后面跟着内存单位 b、k、m、g,分别对应 bytes、KB、MB、和 GB):

  • -m –memory:容器能使用的最大内存大小,最小值为 4m
  • –memory-swap:容器能够使用的 swap 大小
  • –memory-swappiness:默认情况下,主机可以把容器使用的匿名页(anonymous page)swap 出来,你可以设置一个 0-100 之间的值,代表允许 swap 出来的比例
  • –memory-reservation:设置一个内存使用的 soft limit,如果 docker 发现主机内存不足,会执行 OOM 操作。这个值必须小于 –memory 设置的值
  • –kernel-memory:容器能够使用的 kernel memory 大小,最小值为 4m。
  • –oom-kill-disable:是否运行 OOM 的时候杀死容器。只有设置了 -m,才可以把这个选项设置为 false,否则容器会耗尽主机内存,而且导致主机应用被杀死

关于 –memory-swap 的设置必须解释一下,–memory-swap 必须在 –memory 也配置的情况下才能有用。

  • 如果 –memory-swap 的值大于 –memory,那么容器能使用的总内存(内存 + swap)为 –memory-swap 的值,能使用的 swap 值为 –memory-swap 减去 –memory 的值
  • 如果 –memory-swap 为 0,或者和 –memory 的值相同,那么容器能使用两倍于内存的 swap 大小,如果 –memory 对应的值是 200M,那么容器可以使用 400M swap
  • 如果 –memory-swap 的值为 -1,那么不限制 swap 的使用,也就是说主机有多少 swap,容器都可以使用

如果限制容器的内存使用为 64M,在申请 64M 资源的情况下,容器运行正常(如果主机上内存非常紧张,并不一定能保证这一点):

➜  docker run --rm -it -m 64m stress --vm 1 --vm-bytes 64M --vm-hang 0
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 67108864 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: dbug: [7] sleeping forever with allocated memory
.....

而如果申请 100M 内存,会发现容器里的进程被 kill 掉了(worker 7 got signal 9,signal 9 就是 kill 信号)

➜  docker run --rm -it -m 64m stress --vm 1 --vm-bytes 100M --vm-hang 0
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
stress: info: [1] dispatching hogs: 0 cpu, 0 io, 1 vm, 0 hdd
stress: dbug: [1] using backoff sleep of 3000us
stress: dbug: [1] --> hogvm worker 1 [7] forked
stress: dbug: [7] allocating 104857600 bytes ...
stress: dbug: [7] touching bytes in strides of 4096 bytes ...
stress: FAIL: [1] (415) <-- worker 7 got signal 9
stress: WARN: [1] (417) now reaping child worker processes
stress: FAIL: [1] (421) kill error: No such process
stress: FAIL: [1] (451) failed run completed in 0s

关于 swap 和 kernel memory 的限制就不在这里过多解释了,感兴趣的可以查看官方的文档。

内存信息的 cgroups 文件

对于 docker 来说,它的内存限制也是存放在 cgroups 文件系统的。对于某个容器,你可以在 sys/fs/cgroup/memory/docker/ 目录下看到容器内存相关的文件:

➜  ls /sys/fs/cgroup/memory/docker/b067fa0c58dcdd4fa856177fac0112655b605fcc9a0fe07e36950f0086f62f46 
cgroup.clone_children  memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes        memory.soft_limit_in_bytes  notify_on_release
cgroup.event_control   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.move_charge_at_immigrate  memory.stat                 tasks
cgroup.procs           memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.numa_stat                 memory.swappiness
memory.failcnt         memory.kmem.slabinfo            memory.kmem.usage_in_bytes          memory.oom_control               memory.usage_in_bytes
memory.force_empty     memory.kmem.tcp.failcnt         memory.limit_in_bytes               memory.pressure_level            memory.use_hierarchy

而上面的内存限制对应的文件是 memory.limit_in_bytes:

➜  cat /sys/fs/cgroup/memory/docker/b067fa0c58dcdd4fa856177fac0112655b605fcc9a0fe07e36950f0086f62f46/memory.limit_in_bytes
67108864

3、IO 资源(磁盘)

对于磁盘来说,考量的参数是容量和读写速度,因此对容器的磁盘限制也应该从这两个维度出发。目前 docker 支持对磁盘的读写速度进行限制,但是并没有方法能限制容器能使用的磁盘容量(一旦磁盘 mount 到容器里,容器就能够使用磁盘的所有容量)。

➜  ~ docker run -it --rm ubuntu:16.04 bash

root@5229f756523c:/# time $(dd if=/dev/zero of=/tmp/test.data bs=10M count=100 && sync)
100+0 records in
100+0 records out
1048576000 bytes (1.0 GB) copied, 3.82859 s, 274 MB/s

real    0m4.124s
user    0m0.000s
sys 0m1.812s

限制磁盘的权重

通过 –blkio-weight 参数可以设置 block 的权重,这个权重和 –cpu-shares 类似,它是一个相对值,取值范围是 10-1000,当多个 block 去屑磁盘的时候,其读写速度和权重成反比。

不过在我的环境中,–blkio-weight 参数虽然设置了对应的 cgroups 值,但是并没有作用,不同 weight 容器的读写速度还是一样的。github 上有一个对应的 issue,但是没有详细的解答。

–blkio-weight-device 可以设置某个设备的权重值,测试下来虽然两个容器同时读的速度不同,但是并没有按照对应的比例来限制。

限制磁盘的读写速率

除了权重之外,docker 还允许你直接限制磁盘的读写速率,对应的参数有:

  • –device-read-bps:磁盘每秒最多可以读多少比特(bytes)
  • –device-write-bps:磁盘每秒最多可以写多少比特(bytes)

上面两个参数的值都是磁盘以及对应的速率,格式为 :[unit],device-path 表示磁盘所在的位置,限制 limit 为正整数,单位可以是 kb、mb 和 gb。

比如可以把设备的度速率限制在 1mb:

$ docker run -it --device /dev/sda:/dev/sda --device-read-bps /dev/sda:1mb ubuntu:16.04 bash
root@6c048edef769:/# cat /sys/fs/cgroup/blkio/blkio.throttle.read_bps_device 
8:0 1048576
root@6c048edef769:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=5M count=10
10+0 records in
10+0 records out
52428800 bytes (52 MB) copied, 50.0154 s, 1.0 MB/s

从磁盘中读取 50m 花费了 50s 左右,说明磁盘速率限制起了作用。

另外两个参数可以限制磁盘读写频率(每秒能执行多少次读写操作):

  • –device-read-iops:磁盘每秒最多可以执行多少 IO 读操作
  • –device-write-iops:磁盘每秒最多可以执行多少 IO 写操作

上面两个参数的值都是磁盘以及对应的 IO 上限,格式为 :,limit 为正整数,表示磁盘 IO 上限数。

比如,我们可以让磁盘每秒最多读 100 次:

➜  ~ docker run -it --device /dev/sda:/dev/sda --device-read-iops /dev/sda:100 ubuntu:16.04 bash
root@2e3026e9ccd2:/# dd iflag=direct,nonblock if=/dev/sda of=/dev/null bs=1k count=1000
1000+0 records in
1000+0 records out
1024000 bytes (1.0 MB) copied, 9.9159 s, 103 kB/s

从测试中可以看出,容器设置了读操作的 iops 为 100,在容器内部从 block 中读取 1m 数据(每次 1k,一共要读 1000 次),共计耗时约 10s,换算起来就是 100 iops/s,符合预期结果。

写操作 bps 和 iops 与读类似,这里就不再重复了,感兴趣的可以自己实验。

磁盘信息的 cgroups 文件

容器中磁盘限制的 cgroups 文件位于 /sys/fs/cgroup/blkio/docker/ 目录:

➜  ~ ls /sys/fs/cgroup/blkio/docker/1402c1682cba743b4d80f638da3d4272b2ebdb6dc6c2111acfe9c7f7aeb72917/                               
blkio.io_merged                   blkio.io_serviced                blkio.leaf_weight                blkio.throttle.io_serviced        blkio.time_recursive   tasks
blkio.io_merged_recursive         blkio.io_serviced_recursive      blkio.leaf_weight_device         blkio.throttle.read_bps_device    blkio.weight
blkio.io_queued                   blkio.io_service_time            blkio.reset_stats                blkio.throttle.read_iops_device   blkio.weight_device
blkio.io_queued_recursive         blkio.io_service_time_recursive  blkio.sectors                    blkio.throttle.write_bps_device   cgroup.clone_children
blkio.io_service_bytes            blkio.io_wait_time               blkio.sectors_recursive          blkio.throttle.write_iops_device  cgroup.procs
blkio.io_service_bytes_recursive  blkio.io_wait_time_recursive     blkio.throttle.io_service_bytes  blkio.time                        notify_on_release

其中 blkio.throttle.read_iops_device 对应了设备的读 IOPS,前面一列是设备的编号,可以通过 cat /proc/partitions 查看设备和分区的设备号;后面是 IOPS 上限值:

➜  ~ cat /sys/fs/cgroup/blkio/docker/1402c1682cba743b4d80f638da3d4272b2ebdb6dc6c2111acfe9c7f7aeb72917/blkio.throttle.read_iops_device 
8:0 100

blkio.throttle.read_bps_device 对应了设备的读速率,格式和 IOPS 类似,只是第二列的值为 bps:

➜  ~ cat /sys/fs/cgroup/blkio/docker/9de94493f1ab4437d9c2c42fab818f12c7e82dddc576f356c555a2db7bc61e21/blkio.throttle.read_bps_device 
8:0 1048576

总结

从上面的实验可以看出来,CPU 和内存的资源限制已经是比较成熟和易用,能够满足大部分用户的需求。磁盘限制也是不错的,虽然现在无法动态地限制容量,但是限制磁盘读写速度也能应对很多场景。

至于网络,docker 现在并没有给出网络限制的方案,也不会在可见的未来做这件事情,因为目前网络是通过插件来实现的,和容器本身的功能相对独立,不是很容易实现,扩展性也很差。docker 社区已经有很多呼声,也有 issue 是关于网络流量限制的: issue 26767、issue 37、issue 4763。

资源限制一方面可以让我们为容器(应用)设置合理的 CPU、内存等资源,方便管理;另外一方面也能有效地预防恶意的攻击和异常,对容器来说是非常重要的功能。如果你需要在生产环境使用容器,请务必要花时间去做这件事情。