Cgroup
前言 - Cgroup
Docker 提供了一种资源控制的方式,即 Cgroup
Cgroup 是 Control group 的简写,是 Linux 内核提供的一种限制所使用物理资源的机制,包括 CPU、内存 和 IO 这三大方面,基本覆盖了常见的资源配额和使用量控制
一、对 CPU 的控制
1.使用 stress 工具测试
mkdir /opt/stress
vim /opt/stress/Dockerfile
FROM centos:7
RUN yum -y install wget
RUN wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
'//epel源,可以使用扩展软件包(stress)'
RUN yum -y install stress '//可以指定产生线程,使用循环语句,测试用'
cd /opt/stress/
systemctl restart docker.service '//建议重启docker,不然下面的操作可能会失败,卡在wget'
docker build -t centos:stress . '//生成镜像'
docker ps -a
docker images
'//以下可以使用该镜像为基础产生的容器进行测试:'
2.限制 CPU 使用周期速率
1.查看CPU的资源限制
命令格式:
cat /sys/fs/cgroup/cpu/docker/[容器ID]/cpu.cfs_quota_us
例:
docker run -itd --name test1 centos:stress /bin/bash
cat /sys/fs/cgroup/cpu/docker/2964d084535d0670cdf79233a0d2e2a99620d1f0c93854ac8ca73944d4affd5e/cpu.cfs_quota_us
-1
'//-1代表不进行任何限制'
2.手动修改文件以实现修改CPU限制
echo 20000 > /sys/fs/cgroup/cpu/docker/1a9c837fe4a7fc39b9bcaa21aa3fcf12a49582ebe98e96b82cdab1fae32c3e71/cpu.cfs_quota_us
cat /sys/fs/cgroup/cpu/docker/1a9c837fe4a7fc39b9bcaa21aa3fcf12a49582ebe98e96b82cdab1fae32c3e71/cpu.cfs_quota_us
20000
3.在运行容器的同时指定CPU限制条件
'以下这两个参数一般联合使用,控制容器可以分配到的CPU时钟周期'
'--cpu-period是用来指定容器对CPU的使用要在多长时间内做一次重新分配'
'--cpu-quota是用来指定在这个周期内,最多可以有多少时间来跑这个容器'
例:
'容器进程需要每秒使用单个CPU的0.2秒时间,可以将period设为1000000(1s)'
'若在多核下,允许容器进程完全占用两个CPU,可以将period设置为100000(0.1s)'
docker run -itd --name cpu01 --cpu-period=100000 --cpu-quota=200000 centos:stress
docker exec -it 7c3 bash
cat /sys/fs/cgroup/cpu/cpu.cfs_period_us
100000
cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us
200000
3.多任务比例分享 CPU
当有多个容器任务同时运行时,很难计算 CPU 的使用率,我们可以设置 CPU 按照比例共享 CPU 资源,以实现使用率的动态调整
docker run -itd --name cpu1 --cpu-shares 1024 centos:stress stress -c 10 '//-c 10表示产生10个子线程,测试用'
docker exec -it 624 bash
top '//查看CPU使用率'
--新开一个终端--
docker run -itd --name cpu1 --cpu-shares 512 centos:stress stress -c 10
docker exec -it 6d3 bash
top '//对比CPU使用率,是1:2'
使用【–cpu-shares】,分配两个容器使用CPU资源占用权重为1:2
1.默认情况下,每个Docker容器的CPU份额都是1024,单独一个容器的份额是没有意义的,只有在同时运行多个容器时,容器CPU的加权效果才能体现出来
2.比如以上的两个容器是1:2,在CPU进行时间片分配时,后者比前者多一倍的机会获得CPU的时间片,但是分配的结果取决于当时主机和其他容器的运行状态
3.实际上也无法保证该容器一定获得相应的CPU时间片,因为若是该的进程一直是空闲的,那么cpu1就可以获取比cpu2更多的CPU时间片
4.在极端情况下,例如主机上只运行了一个容器,即使它的CPU份额较小,也是可以独占整个主机的CPU资源的
5.Cgroups只在容器分配的资源紧缺时,即在需要对容器使用的资源进行限制时,才会生效,因此,无法根据某个容器的CPU份额来确定有多少CPU资源分给给它
6.即资源分配的结果取决于同时运行的其他容器CPU分配和容器中进程的运行情况
4.限制 CPU 内核使用
可以通过配置,使得某些程序独享 CPU 内核,以提高其处理速度
cat /sys/fs/cgroup/cpuset/docker/0262a2c1e887/cpuset.cpus
0-7
'//若该服务器有8个核心,那么CPU编号为0-7,表示该容器内的进程在核心上皆可运行,这是默认配置'
'//可查看cat /proc/cpuinfo中的CPU编号(processor)'
docker run -itd --name a1 --cpuset-cpus 0 centos:stress
'//表示创建的容器只能使用0、1两个内核,其后可以使用top命令按1查看CPU使用'
docker exec [容器ID] taskset -c -p 1
'//表示绑定至指定CPU上运行'
在实际使用中,尽量使用绑定内核的方式分配 CPU 资源,然后再配合【–cpu-share】选项来动态调整 CPU 使用资源的比例
住意,这里若是两个容器各自指定不同的CPU内核,则–cpu-share基本没有效果
二、对内存使用的限制
'与操作系统类似,容器可使用的内存包括两部分:物理内存和 Swap'
'-m 或 --memory:设置内存的使用限额'
'--memory-swap:设置内存+swap 的使用限额'
docker run -itd -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M
'//--vm 1:启动1个内存工作线程'
'//--vm-bytes 280M:每个线程分配280M内存'
'相应的Cgroup配置文件:/sys/fs/cgroup/memory/memory.limit_in_bytes'
docker run -itd -m 200M --memory-swap=300M progrium/stress --vm 1 --vm-bytes 280M
'//如果让工作线程分配的内存超过300M,分配的内存超过了限额,stress线程会报错,容器退出'
'注意!一旦容器Cgroup使用的内存超过了限制的容量,Linux内核就会尝试收回这些内存'
'如果仍旧无法控制内存使用在这个设置的范围之内,就会杀死该进程!'
三、对磁盘 IO 资源的限制
如果是在一台服务器上进行容器的混合部署,那么可能会出现同时有几个程序写磁盘数据的情况,那么如何进行限制呢?
bps:byte per second(每秒读写的字节数)
iops:io per second(每秒IO的次数)
1.限制某个程序写入的bps数据量
docker run -d --device-write-bps /dev/sda:30M centos:7
2.限制读取某个程序的bps数据量
docker run -d --device-read-bps /dev/sda:30M centos:7
3.限制读取某个程序的iops次数
--device-read-iops
4.限制写入某个程序的iops次数
--device-write-iops
示例:限制容器写 /dev/sda 的速率为 5MB/s
docker run -it --device-write-bps /dev/sda:5MB centos:7
dd if=/dev/zero of=test bs=1M count=1024 oflag=direct
'//dd复制,从zero拿文件test,每次拿1MB,写1024次即1GB大小'
'oflag=direct表示指定用direct IO 方式写文件,这样--device-write-bps才能生效'
docker run -it centos:7
dd if=/dev/zero of=test bs=1M count=1024 oflag=direct
默认情况下,所有容器能平等地读写磁盘,可以通过设置--blkio-weight参数来改变容器block 10的优先级。
--blkio-weight与--cpu-shares类似,设置的是相对权重值,默认为500
在下面的例子中,容器A读写磁盘的带宽是容器B的两倍。
docker run-it --name container A--blkio-weight 600 centos:stress
cat /sys/fs/cgroup/blkio/blkio.weight
600
docker run-it --name container B--blkio-weight 300 centos:stress
cat /sys/fs/cgroup/blkio/blkio.weight
300
四、TLS 通讯加密
1.密钥
①对称密钥
是一种加密和解密使用相同密钥的算法,效率较高,可加密内容较大,用来加密会话过程中的消息,适用于大规模生产的环境下使用
就像固定的一道门与一把钥匙一样,但是,对称加密算法在分布式网络系统上使用较为困难,因为密钥管理困难,使用成本较高,还是这个比方,你去收租,几百串钥匙你管的烦不烦?
相对于非对称密钥而言,安全性较差,就像捡到了你的钥匙,那么就可以直接来开你的门了
②非对称密钥
其原理是加密密钥与解密密钥不同,形成一个密钥对,其中一个密钥加密的结果,可以用另一个密钥来解密
加密和解密使用不同的密钥,一个密钥公开,称公钥,一个密钥保密,称私钥,相对而言,效率不高,但是安全性高
比如,大家一起去食堂吃饭,付款码就像公钥,每个人都可以看到(使用),私钥就代表着个人身份信息,用自己的私钥去验证,最终成功付款,才能干饭
③混合密钥(对称+非对称)
只用对称密钥进行信息传输,效率虽快,但是安全性差一点,被截胡怎么办?而用非对称密钥,安全性相对而言是有一定保障,但若是传输的信息量较大,光是去加密就需要耗费大量的时间,导致效率低下
因此,聪明人可能就想到了,我们就不能将其混合使用吗,各取所长:对要发送的明文(数据量较大)进行对称加密生成密文,然后对这个加密过程中生成的对称密钥进行非对称加密(私钥加密)
这就像,你用一把钥匙锁起来了一大箱金银财宝,然后将这把钥匙又用一个小匣子单独锁了起来,安全性更强!还要对其进行一个签名,即这个小匣子中还代表着你的身份信息
还有一个步骤,将明文在加密之前进行哈希算法产生一个哈希值,主要是为了验证数据的完整性,从而得知这个数据传过去有无被中间人篡改
最后,将密文、加密后的密钥、哈希值这三样一起传递给对方,对方将加密后的密钥用公钥解密后获取私钥,将密文解密成明文,然后比对哈希值,检查数据有无被篡改(若不同则丢弃)
2.签名
前面有提到过,签名代表着你的身份信息
打个比方,你扫二维码付款,收款商家能够得知是谁付的款,这个对象就是你的身份信息
3.数据完整性
这个前文也有提高过,使用哈希算法确保数据完整性
即这个数据在传输过程结束后能够最终检测其一致性
4.证书
组成:密钥+身份信息+哈希算法等,统统在其中,就像容器一样
格式:x509,国际标准
证书是需要被颁发的(证书颁发机构:CA),拿到 ca 证书后才能去找 CA 给自己颁发服务器证书(server,包含公钥),还需要有客户端证书(给客户端的,包含私钥)。
五、Docker 启用 TLS 进行加密通讯
1.TLS 概述
TLS(传输层安全协议)用于在两个通信应用程序之间提供保密性和数据完整性,该协议由两层组成:TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)
TLS 协议采用主从式架构模型,用于在两个应用程序间透过网络创建起安全的连线,防止在交换数据时受到窃听及篡改
TLS 协议的优势是与高层的应用层协议(如 HTTP、FTP 等)无耦合;应用层协议能透明地运行在 TLS 协议之上,由 TLS 协议进行创建加密通道需要的协商和认证;应用层协议传送的数据在通过TLS协议时都会被加密,从而保证通信的私密性
2.TLS 原理
TLS 协议是可选的,必须配置客户端和服务器才能使用。主要有两种方式实现这一目标:一个是使用统一的 TLS 协议通信端口(例如:用于 HTTPS 的 443 端口);另一个是客户端请求服务器连接到 TLS 时使用特定的协议机制
一旦客户端和服务器都同意使用 TLS 协议,他们通过使用一个握手过程协商出一个有状态的连接以传输数据。通过握手,客户端和服务器协商各种参数用于创建安全连接:
当客户端连接到支持 TLS 协议的服务器要求创建安全连接并列出了受支持的密码组合(加密密码算法和加密哈希函数),握手开始
服务器从该列表中决定加密和散列函数,并通知客户端
服务器发回其数字证书,此证书通常包含服务器的名称、受信任的证书颁发机构(CA)和服务器的公钥
客户端确认其颁发的证书的有效性
为了生成会话密钥用于安全连接,客户端使用服务器的公钥加密随机生成的密钥,并将其发送到服务器,只有服务器才能使用自己的私钥解密
利用随机数,双方生成用于加密和解密的对称密钥。这就是 TLS 协议的握手,握手完毕后的连接是安全的,直到连接(被)关闭。如果上述任何一个步骤失败,TLS 握手过程就会失败,并且断开所有的连接
2.部署步骤
'//服务端主机'
--开局优化--
mkdir /tls
cd /tls/
hostnamectl set-hostname server
su
'//设置主机名称,便于识别'
cat >> /etc/hosts << EOF
> 192.168.78.11 server
> 192.168.78.22 client
> EOF '//添加映射'
'//切换至client客户端'
hostnamectl set-hostname client
su
cat >> /etc/hosts << EOF
> 192.168.126.16 server
> 192.168.126.17 client
> EOF
ping server
'//测试能否互相ping通'
ping client
--server端--
1.创建CA密钥
openssl genrsa -aes256 -out ca-key.pem 4096 '//输入密钥,123123(确认)'
'//genrsa:非对称密钥;-aes256:256位密钥长度'
'//-out输出名为ca-key.pem,为证书名称,结尾固定为.pem,文件大小为4096(默认)'
ls
ca-key.pem
2.创建CA证书
openssl req -new -x509 -days 1000 -key ca-key.pem -sha256 -subj "/CN=*" -out ca.pem '//输入123123(CA密钥)'
'//req:请求;-new创建一个新的证书,-x509为证书标准规格'
'//-days:证书有效期为1000天;使用到之前生成的CA密钥文件'
'//使用哈希;项目名称;-out产生一个CA证书'
ls
ca-key.pem ca.pem '//证书和密钥是一对,相互之间是不可分割的'
3.创建服务器私钥
openssl genrsa -out server-key.pem 4096
'//生成服务端密钥,长度为4096,这里是空密码生成'
ls
ca-key.pem ca.pem server-key.pem
'//有兴趣的同学可以进去看看,空也是一个字符,能对其加密'
4.签名服务器私钥(添加身份信息)
openssl req -subj "/CN=*" -sha256 -new -key server-key.pem -out server.csr
'//请求的名称;哈希校验;生成新文件;引用服务器私钥'
'签名文件都是以.csr为结尾'
ls
ca-key.pem ca.pem server.csr server-key.pem
5.使用CA证书与私钥证书签名,制作server端证书
openssl x509 -req -days 1000 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem
'//-in输入server端签名,CA证书,CA密钥'
'//使用CA证书颁发,生成服务端证书'
ls '//'
ca-key.pem ca.pem ca.srl server-cert.pem server.csr server-key.pem
6.生成客户端密钥
openssl genrsa -out key.pem 4096
7.签名客户端
openssl req -subj "/CN=client" -new -key key.pem -out client.csr
ls
ca-key.pem ca.srl key.pem server.csr
ca.pem client.csr server-cert.pem server-key.pem
8.创建配置文件
echo extendedKeyUsage=clientAuth > extfile.cnf
'//扩展性的密钥使用是使用客户端密钥的方式,生成这个配置文件,生成客户端时需要使用此文件(后一步)'
9.签名证书,需要(签名客户端,ca证书,ca密钥)
openssl x509 -req -days 1000 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf
'//生成客户端证书'
ls
ca-key.pem ca.srl client.csr key.pem server.csr
ca.pem cert.pem extfile.cnf server-cert.pem server-key.pem
10.删除多余文件
rm -rf ca.srl client.csr extfile.cnf server.csr
'//签名文件在过程中产生,也可以说是多余的'
ls '//可以看到留下来的都是证书'
ca-key.pem ca.pem cert.pem key.pem server-cert.pem server-key.pem
11.配置docker
vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --tlsverify --tlscacert=/tls/ca.pem --tlscert=/tls/server-cert.pem --tlskey=/tls/server-key.pem -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock
'//注释第13行,在下面添加以上语句'
'//使用/usr/bin/dockerd(该进程)启动;--tlsverify:安全加密通讯'
'//tls中的匹配证书...;server端的证书及密钥'
'//监听任意地址,从端口2376来进行访问;通讯文件进程'
12.重启进程(服务/配置文件)及docker服务
systemctl daemon-reload && systemctl restart docker
netstat -natp|grep 2376
tcp6 0 0 :::2376 :::* LISTEN 101431/dockerd
13.远程传输CA证书、客户端证书、客户端密钥至client端
scp ca.pem root@192.168.78.22:/etc/docker/
scp cert.pem root@192.168.78.22:/etc/docker/
scp key.pem root@192.168.78.22:/etc/docker/
14.本地验证
docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H tcp://server:2376 version
'//docker[...]version,本地使用证书进行验证,注意是使用相对路径'
--client端--
'//切至客户端'
cd /etc/docker/
ls
ca.pem cert.pem daemon.json key.json key.pem
docker --tlsverify --tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem -H tcp://server:2376 version
'//验证tls安全加密,注意这里server不要换成ip地址'