docker 容器的安全很大程度上依賴 linux 本身,因爲是共享宿主機內核。
docker 安全評估主要考慮以下幾個方面:
- linux 內核的命名空間(namespace)機制提供的容器隔離安全
- linux 控制組(cgroup)對容器資源的控制能力安全
- linux 內核的能力機制所帶來的操作系統安全
- docker 程序(主要是服務器端)本身的抗攻擊能力
- 其他安全增強機制的影響
Docker安全評估
- 命名空間隔離安全: docker run 啓動一個容器時,後臺會爲容器創建一個獨立的命名空間,
這是最基礎的隔離,讓容器作爲一個獨立的個體存在
[root@server1 ~]# docker run -it --name vm1 ubuntu
root@ed7c89bbbfe3:/# # ctrl + p + q 退出,讓容器保持運行
[root@server1 ~]# docker inspect vm1 |grep Pid # 容器其實就是一個進程,查看他的pid
"Pid": 5279,
"PidMode": "",
"PidsLimit": 0,
For more details see ps(1).
[root@server1 ~]# ps ax | grep 5279
5279 pts/0 Ss+ 0:00 /bin/bash # 這時5279這個進程打開的bash
5346 pts/0 R+ 0:00 grep --color=auto 5279
[root@server1 ~]# cd /proc/5279/ns/ ## 進入它的命名空間
[root@server1 ns]# ls
ipc mnt net pid user uts # 這裏就是命名空間中的內容
- 但是這種方式與傳統虛擬機相比,隔離的不徹底,因爲容器就是一個進程,那麼多個容器就還 是共用宿主機的內核
- 在 linux 內核中,有些資源和對象不能被 namespace 化,如:時間,這樣機不能保證完全隔離了
- 控制組資源控制安全: docker run 啓動一個容器時,後臺會爲容器創建一個獨立的控制組
策略集合
[root@server1 ns]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
## 可以看到控制組的策略集合,
[root@server1 ~]# cd /sys/fs/cgroup/cpu/docker/
[root@server1 docker]# ls
cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares ed7c89bbbfe3ac5c6efef492b919eef7b108e655964b3e956ce197201a696e7e tasks
cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat notify_on_release
## 可以看到 cpu 資源控制組爲 docker 容器分配的 cpu 資源
裏面的 ed7c89bbbfe3ac5c6efef492b919eef7b108e655964b3e956ce197201a696e7e 就是我們剛纔啓動的一個容器隨機生成的.
[root@server1 ed7c89bbbfe3ac5c6efef492b919eef7b108e655964b3e956ce197201a696e7e]# ls
cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
而且裏面的參數時完全繼承上機目錄的,也就係統中的資源
- linux cgroup 還提供了很多有用特性,確保容器可以公平分享主機內存、cpu 等資源
- 確保當容器內資源壓力不會影響到宿主機和其他容器,在 DDos 方面必不可少
- 內核能力機制 : 是 linux 內核的一個強大特性,可提供細粒度的權限訪問控制
大部分情況下,容器並不需要真正的 root 權限,只要少數能力即可
[root@server1 ~]# docker attach vm1 # 連上剛纔的容器
root@ed7c89bbbfe3:/# id
uid=0(root) gid=0(root) groups=0(root) # 看出是超級用戶,但是沒有超戶的權力
root@ed7c89bbbfe3:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
root@ed7c89bbbfe3:/# ip link set down eth0
RTNETLINK answers: Operation not permitted #但是想要關閉網卡,不行,雖然是 root
-
docker 服務端的防護:確保只有可信的用戶才能訪問到 docker 服務;將容器的 root 用戶映射到本地主機的非 root 用戶,減輕容器和主機之間因權限提升而引起的安全問題;允許docker 服務端在非 root 權限下運行,利用安全可靠的子進程來代理執行需要特權的操作(子進程只允許在特定範圍內操作)
-
其他安全特性:使用有增強安全特性的容器模板;用戶可以定義更加嚴格的訪問控制機制等
(比如文件系統掛載到容器內部時,設置只讀)
容器資源控制
- linux Cgroups:組資源控制是限制一個進程組使用資源的上限,包括 cpu、內存、磁盤、帶寬等
[root@server1 ~]# mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup /lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup
cgroup on /sys/fs/cgroup/perf_event type cgroup
cgroup on /sys/fs/cgroup/hugetlb type cgroup
cgroup on /sys/fs/cgroup/devices type cgroup
cgroup on /sys/fs/cgroup/pids type cgroup
cgroup on /sys/fs/cgroup/memory type cgroup
cgroup on /sys/fs/cgroup/blkio type cgroup
cgroup on /sys/fs/cgroup/freezer type cgroup
cgroup on /sys/fs/cgroup/cpuset type cgroup
## 看到都掛載在/sys/fs/cgroup 下
[root@server1 ~]# cat /etc/security/limits.conf
## 我們在這裏也可以設置進程數,內存等,但是不是很準確,因爲還有 swap 分區,swap 分區也是可以使用的。
所以我們可以在 docker 啓動是指定使用的內存,cpu 等:
cpu限制
## 使用 docker run --cpu --memory 進行指定就可以了
那我們如何在系統中進行設定那:
[root@server1 ~]# cd /sys/fs/cgroup/
[root@server1 cgroup]# ls
blkio cpu cpuacct cpu,cpuacct cpuset devices freezer hugetlb memory net_cls net_cls,net_prio net_prio perf_event pids systemd
# 這個目錄下都是限制系統各項內容的,有磁盤IO,cpu 內存,設備等。
裏面的子目錄也叫子系統。這裏面每個子系統都有docker容器的目錄。
我們可以在在每個子系統下,爲每個進程創建一個控制組。
[root@server1 cpu]# mkdir a1
[root@server1 cpu]# ls a1/
cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
[root@server1 cpu]# cat cpu.rt_period_us
1000000
[root@server1 cpu]# cat a1/cpu.rt_period_us
1000000
# 我們在cpu子系統下建立一個控制組,可以看出子控制器和父級值是一樣的,父級是針對所有控制器,子控制器的值可修改,針對某進程。
我們現在在操作系統層面控制(也可以在容器內)
[root@server1 cpu]# yum install libcgroup-tools.x86_64 -y #安裝 cgroup 命令行工具
[root@server1 cpu]# cat cpu.cfs_period_us
100000 # cpu 調用週期,單位:微秒
[root@server1 cpu]# cat cpu.cfs_quota_us
-1 # 配額限制,-1 表示不限制
[root@server1 cpu]# cd a1/
[root@server1 a1]# echo 20000 > cpu.cfs_quota_us # 表示 100000 微秒的週期內,只能使用 20000,也就是 20%
[root@server1 a1]# cat cpu.cfs_quota_us
20000
驗證:
[root@server1 a1]# dd if=/dev/zero of=/dev/null &
[1] 5796
# 這是一個無限循環的進程,不會佔用磁盤和 IO,只會消耗 cpu
我們發現佔用了幾乎100的cpu,這是因爲我們並沒有將這個進程和這個限制關聯起來,
所以:
[root@server1 a1]# echo 5796 > tasks
# 讓進程 id 和 tasks 關聯,tasks 裏面是那個進程,這個資源組就控制的誰
再次top查看:
這次就限制到了20%。
那末我們在手動指定容器試一下:
[root@server1 a1]# docker run -it --name vm2 --cpu-period 100000 --cpu-quota 20000 ubuntu
## 指定配額爲20000 CTRL +P+Q 先退出,在docker資源組中查看:
[root@server1 a1]# cd ../docker/
[root@server1 docker]# cd fc8692dd94d170ff83dd274e28049eb68a617733e91dda6145a171541b535101/
## 進入我們的容器VM2的目錄
[root@server1 fc8692dd94d170ff83dd274e28049eb68a617733e91dda6145a171541b535101]# ls
cgroup.clone_children cgroup.procs cpuacct.usage cpu.cfs_period_us cpu.rt_period_us cpu.shares notify_on_release
cgroup.event_control cpuacct.stat cpuacct.usage_percpu cpu.cfs_quota_us cpu.rt_runtime_us cpu.stat tasks
[root@server1 fc8692dd94d170ff83dd274e28049eb68a617733e91dda6145a171541b535101]# cat cpu.cfs_quota_us
20000
查看,卻是是20000,所以對容器的限制就是我們在開啓是加上參數就行了
我們再再容器中用dd 做測試:
[root@server1 cpu]# docker attach vm2
root@fc8692dd94d1:/# dd if=/dev/zero of=/dev/null &
[1] 15
## ctrl +p +q 退出
可以看到,對容器裏面的進程也是其效果的,和剛纔在命令行中的dd 一樣,都只佔用了20%的cpu。
內存限制
容器可用內存包括:物理內存、swap 分區(操作系統也是)。
- 但是一旦切換到 swap 分區,性能就不能保證了,因爲 swap 是物理硬盤,當然沒有內存快’
- 容器的資源限制很簡單 docker run -it --memory 256M --memory-swap=256M ubuntu 就可以了
操作系統層面的限制:
[root@server1 memory]# cd /sys/fs/cgroup/memory/
[root@server1 memory]# mkdir a2
[root@server1 memory]# cd a2
[root@server1 a2]# ls
cgroup.clone_children memory.kmem.failcnt memory.kmem.tcp.limit_in_bytes memory.max_usage_in_bytes memory.move_charge_at_immigrate memory.stat tasks
cgroup.event_control memory.kmem.limit_in_bytes memory.kmem.tcp.max_usage_in_bytes memory.memsw.failcnt memory.numa_stat memory.swappiness
cgroup.procs memory.kmem.max_usage_in_bytes memory.kmem.tcp.usage_in_bytes memory.memsw.limit_in_bytes memory.oom_control memory.usage_in_bytes
memory.failcnt memory.kmem.slabinfo memory.kmem.usage_in_bytes memory.memsw.max_usage_in_bytes memory.pressure_level memory.use_hierarchy
memory.force_empty memory.kmem.tcp.failcnt memory.limit_in_bytes memory.memsw.usage_in_bytes memory.soft_limit_in_bytes notify_on_release
[root@server1 a2]# cat memory.limit_in_bytes # 這裏是內存的最大限制,默認是不限制的
9223372036854771712
那我們限制最大隻能使用256M: 256*1024*1024=268435456
[root@server1 a2]# echo 268435456 > memory.limit_in_bytes
[root@server1 a2]# cat memory.limit_in_bytes
268435456
測試:
[root@server1 shm]# df -H
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/rhel-root 19G 3.0G 16G 16% /
devtmpfs 508M 0 508M 0% /dev
tmpfs 520M 0 520M 0% /dev/shm
#這個目錄掛載的是內存的 1/2,當前主機是 1G 內存,那麼這裏就是 512M,我們用它佔用內存
[root@server1 shm]# free -m
total used free shared buff/cache available
Mem: 991 131 356 12 502 689 # 內存當前可用爲689M
Swap: 2047 0 2047 # swap 分區爲2047M
進行截取:
[root@server1 shm]# cd /dev/shm/ # 進入這個目錄
[root@server1 shm]# ls
[root@server1 shm]# dd if=/dev/zero of=bigfile bs=1M count=100 # 截取100M
100+0 records in
100+0 records out
104857600 bytes (105 MB) copied, 0.0557332 s, 1.9 GB/s
[root@server1 shm]# ls
bigfile
[root@server1 shm]# free -m
total used free shared buff/cache available
Mem: 991 129 258 112 603 592 #確實少了100
Swap: 2047 0 2047
[root@server1 shm]# dd if=/dev/zero of=bigfile bs=1M count=200
200+0 records in
200+0 records out
209715200 bytes (210 MB) copied, 0.125939 s, 1.7 GB/s # 截取200M
[root@server1 shm]# free -m
total used free shared buff/cache available
Mem: 991 129 158 212 703 491
Swap: 2047 0 2047
[root@server1 shm]# dd if=/dev/zero of=bigfile bs=1M count=300 # 截取300M
300+0 records in
300+0 records out
314572800 bytes (315 MB) copied, 0.164207 s, 1.9 GB/s
[root@server1 shm]# free -m
total used free shared buff/cache available
Mem: 991 130 70 312 790 392
Swap: 2047 1 2046
## 我們發現,剛剛明明設置了256M,這裏卻300M都可以使用了,
還是因爲我們沒有進行綁定。
##剛纔裝的 cgexec 命令,可以直接在命令行控制資源組
[root@server1 shm]# cgexec -g memory:a2 dd if=/dev/zero of=bigfile bs=1M
## memory和a2這個控制器進行綁定
[root@server1 shm]# free -m
total used free shared buff/cache available
Mem: 991 129 270 112 590 591 # 少了100M
Swap: 2047 0 2047
[root@server1 shm]# cgexec -g memory:a2 dd if=/dev/zero of=bigfile bs=1M count=300
300+0 records in # 截取300M
300+0 records out
314572800 bytes (315 MB) copied, 0.185631 s, 1.7 GB/s
[root@server1 shm]# free -m
total used free shared buff/cache available
Mem: 991 130 115 266 744 436 # 這裏至少了253 M 確實限制再了256M之內
Swap: 2047 46 2001
[root@server1 shm]# ls
bigfile
但是這樣的文件竟然創建成功了,並沒有報錯,而且我們發現swap分區少了36M,
這就說明:多出去的不能使用內存,所以使用了 swap,那我們怎麼徹底限制哪?
[root@server1 shm]# cd /sys/fs/cgroup/memory/a2
[root@server1 a2]# cat memory.memsw.limit_in_bytes
9223372036854771712 # 我們發現swap分也是沒有被限制的
[root@server1 a2]# echo 268435456 > memory.memsw.limit_in_bytes # 也限制爲256M
-bash: echo: write error: Device or resource busy # 失敗時因爲我們剛纔使用到了swap分區
[root@server1 a2]# rm -fr /dev/shm/bigfile
[root@server1 a2]# echo 268435456 > memory.memsw.limit_in_bytes
## 現在就可以了。改完後表示物理內存和 swap 一共使用不能超過 256M
#測試:
[root@server1 shm]# cgexec -g memory:a2 dd if=/dev/zero of=bigfile bs=1M count=300
Killed
發現這個就被殺掉了,說明限制成功
block io限制,對磁盤讀寫的限制
[root@server1 shm]# cd /sys/fs/cgroup/blkio/
[root@server1 blkio]# mkdir a3
[root@server1 blkio]# cd a3/
[root@server1 a3]# ls
blkio.io_merged blkio.io_service_bytes_recursive blkio.io_wait_time blkio.sectors blkio.throttle.read_iops_device blkio.weight notify_on_release
blkio.io_merged_recursive blkio.io_serviced blkio.io_wait_time_recursive blkio.sectors_recursive blkio.throttle.write_bps_device blkio.weight_device tasks
blkio.io_queued blkio.io_serviced_recursive blkio.leaf_weight blkio.throttle.io_service_bytes blkio.throttle.write_iops_device cgroup.clone_children
blkio.io_queued_recursive blkio.io_service_time blkio.leaf_weight_device blkio.throttle.io_serviced blkio.time cgroup.event_control
blkio.io_service_bytes blkio.io_service_time_recursive blkio.reset_stats blkio.throttle.read_bps_device blkio.time_recursive cgroup.procs
# blkio.throttle.read_bps_device 每秒讀的數據量
# blkio.throttle.read_iops_device 每秒的 io 操作次數
## '設置每秒寫入數據量爲 1M'
[root@server1 a3]# fdisk -l
Device Boot Start End Blocks Id System
/dev/sda1 * 2048 2099199 1048576 83 Linux
/dev/sda2 2099200 41943039 19921920 8e Linux LVM
# 我們當前使用的磁盤是 /dev/sda
[root@server1 a3]# ll /dev/sda
brw-rw---- 1 root disk 8, 0 May 27 11:37 /dev/sda ##看到磁盤的主號和輔號(8 和 0)
[root@server1 a3]# echo "8:0 1048576" > blkio.throttle.write_bps_device
#1024 * 1024 = 1048576
[root@server1 ~]# cgexec -g blkio:a3 dd if=/dev/zero of=testfile bs=1M count=10
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 0.00391746 s, 2.7 GB/s
## 沒有生效,速率爲2.7G/s
這是因爲目前 block io 限制只對 direct io 有效(不使用文件緩存)
[root@server1 ~]# cgexec -g blkio:a3 dd if=/dev/zero of=testfile bs=1M count=10 oflag=direct
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 10.0016 s, 1.0 MB/s # 生效了,耗時10s
那我們docker中怎樣去限制哪?
在使用容器時也有相關參數,我們在使用時直接docker run 加上參數就好了:
[root@server1 a3]# docker run --help |grep device
--blkio-weight-device list Block IO weight (relative device weight) (default [])
--device list Add a host device to the container
--device-cgroup-rule list Add a rule to the cgroup allowed devices list
--device-read-bps list Limit read rate (bytes per second) from a device (default [])
--device-read-iops list Limit read rate (IO per second) from a device (default [])
--device-write-bps list Limit write rate (bytes per second) to a device (default [])
--device-write-iops list Limit write rate (IO per second) to a device (default [])
[root@server1 ~]# docker run -it --name vm1 --device-write-bps /dev/sda:1MB ubuntu #創建容器
root@36784d59d0f5:/# dd if=/dev/zero of=testfile bs=1M count=10 oflag=direct # 截取文件
10+0 records in
10+0 records out
10485760 bytes (10 MB) copied, 10.2338 s, 1.0 MB/s # 限制生效了
## ctrl + p + q 退出
# 那我們去操作系統層面去查看
[root@server1 ~]# cd /sys/fs/cgroup/blkio/docker/
[root@server1 docker]# cd 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139/
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat blkio.throttle.write_bps_device
8:0 1048576 # 和之前手動寫入到 a3 中的一樣
其它限制
[root@server1 ~]# cd /sys/fs/cgroup/freezer # 管控系統進程暫停與恢復的
[root@server1 freezer]# cd docker/36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139/
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat tasks
6485 ## 查看進程號
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat freezer.state
THAWED ## 查看進程狀態,THAWED表示活躍的
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# docker pause vm1
vm1 ## 凍結vm1
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat freezer.state
FROZEN ## 狀態爲凍結
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# docker attach vm1
You cannot attach to a paused container, unpause it first ## 嘗試連接被拒絕
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat tasks
6485 ## 但是進程依然存在
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# ps ax
6485 pts/0 Ds+ 0:00 /bin/bash ## D 表示凍結,暫停
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# docker unpause vm1
vm1
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# cat freezer.state
THAWED
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# ps ax |grep bash
6485 pts/0 Ss+ 0:00 /bin/bash
[root@server1 36784d59d0f5841ba3c22be228e97aa537f87eb39915a7568046bf7151c04139]# docker attach vm1
root@36784d59d0f5:/# # 就可以連接了,然後退出
[root@server1 ~]# docker container prune ## 刪除所有退出的容器