Copyright © 2020 Powered by MWeb, Theme used GitHub CSS.
获得容器的运行状态主要有两种方法,一种是通过docker cli提供的stats命令查看容器的状态。另一种就是通过docker api
通过docker stats可以获得所有的容器的状态:
默认情况下,Docker daemon监听unix://var/run/docker.sock,并且客户端必须有root权限用来与daemon交互。
为了使用Docker REST API,可以修改docker配置,添加-H标记开启远程访问:
DOCKER_OPTS="$DOCKER_OPTS -H=0.0.0.0:4232"
此处开启远程访问只是方便测试,但会因为没有添加权限验证留下安全隐患
然后就可以用过docker api与docker daemon交互:
root@ubuntu:~# curl localhost:4232/version | python -mjson.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 199 100 199 0 0 19638 0 --:--:-- --:--:-- --:--:-- 19900
{
"ApiVersion": "1.23",
"Arch": "amd64",
"BuildTime": "2016-06-01T21:47:50.269346868+00:00",
"GitCommit": "b9f10c9",
"GoVersion": "go1.5.4",
"KernelVersion": "3.16.0-30-generic",
"Os": "linux",
"Version": "1.11.2"
}
而通过remote api获得容器状态比较简单,只需要通过 /containers/<id or name>/stats
便可以获得容器状态。而且这个api会每秒一次的返回最新状态。
当然,如果只是想获得一次状态也是很简单的,这个api支持一个stream参数,支持 True/true/1
或 False/false/0
两种只,前者会让这个api每秒返回一次状态,后者只会让这个api返回一次。默认是前者。
root@ubuntu:~# curl localhost:4232/containers/d81984a3c60d/stats?stream=0 | python -mjson.tool
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 1592 100 1592 0 0 1018 0 0:00:01 0:00:01 --:--:-- 1018
{
"blkio_stats": {
"io_merged_recursive": [],
"io_queue_recursive": [],
"io_service_bytes_recursive": [],
"io_service_time_recursive": [],
"io_serviced_recursive": [],
"io_time_recursive": [],
"io_wait_time_recursive": [],
"sectors_recursive": []
},
"cpu_stats": {
"cpu_usage": {
"percpu_usage": [
348564780
],
"total_usage": 348564780,
"usage_in_kernelmode": 10000000,
"usage_in_usermode": 0
},
"system_cpu_usage": 3000540000000,
"throttling_data": {
"periods": 0,
"throttled_periods": 0,
"throttled_time": 0
}
},
"memory_stats": {
"failcnt": 0,
"limit": 1041981440,
"max_usage": 6668288,
"stats": {
"active_anon": 6610944,
"active_file": 4096,
"cache": 32768,
"hierarchical_memory_limit": 18446744073709551615,
"inactive_anon": 12288,
"inactive_file": 0,
"mapped_file": 0,
"pgfault": 1133,
"pgmajfault": 0,
"pgpgin": 669,
"pgpgout": 584,
"rss": 6594560,
"rss_huge": 6291456,
"total_active_anon": 6610944,
"total_active_file": 4096,
"total_cache": 32768,
"total_inactive_anon": 12288,
"total_inactive_file": 0,
"total_mapped_file": 0,
"total_pgfault": 1133,
"total_pgmajfault": 0,
"total_pgpgin": 669,
"total_pgpgout": 584,
"total_rss": 6594560,
"total_rss_huge": 6291456,
"total_unevictable": 0,
"total_writeback": 0,
"unevictable": 0,
"writeback": 0
},
"usage": 6627328
},
"networks": {
"eth0": {
"rx_bytes": 2592,
"rx_dropped": 0,
"rx_errors": 0,
"rx_packets": 32,
"tx_bytes": 648,
"tx_dropped": 0,
"tx_errors": 0,
"tx_packets": 8
}
},
"pids_stats": {},
"precpu_stats": {
"cpu_usage": {
"percpu_usage": [
347925623
],
"total_usage": 347925623,
"usage_in_kernelmode": 10000000,
"usage_in_usermode": 0
},
"system_cpu_usage": 2999540000000,
"throttling_data": {
"periods": 0,
"throttled_periods": 0,
"throttled_time": 0
}
},
"read": "2016-10-08T00:05:40+08:00"
}
在说端口之前,先明确下docker 容器的网络,可以用过docker network
命令常看docker的网络:
# docker network ls
NETWORK ID NAME DRIVER
33b01b58a9a2 bridge bridge
ffe4299ed1a9 host host
fc93a0e85b75 none null
可以看到,docker中包含bridge,host,none三种网络,其实除了这三种,还有container第四种网络。
其中,bridge是默认的网络,也就是在不指定网络模式的时候docker run
使用的的网络模式,而none,正如其名,是不分配任何网络。host模式和container模式不是那么常用,前者是使用宿主机的网络,而后者是和指定的容器共享网络。
而这所讨论的容器端口与主机端口映射的原理,就是docker默认的bridge网络模式。
当docker run
一个容器,可以使用-p
参数指定一个开放端口,甚至可以把这个端口映射的主机的某一端口上。比如,可以通过以下命令将nginx容器的80端口映射到主机的8080端口上:
# docker run -d -p 8080:80 --name nginx daocloud.io/library/nginx
81ea762a6bb99c1ee8c72d0fcc91f5151f8f65cf7c26747dd1f64d260845e21c
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
81ea762a6bb9 daocloud.io/library/nginx "nginx -g 'daemon off" 13 seconds ago Up 12 seconds 443/tcp, 0.0.0.0:8080->80/tcp nginx
可以尝试访问宿主机的8080端口,可以看到是一个运行的ngxin。
使用docker inspect
命令可以看到nginx容器网络相关的配置:
"NetworkSettings": {
"Bridge": "",
"SandboxID": "0af9e9bf575206c247c0c0e1915dfec533dc173ae55917662e8c08df19441fe3",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {
"443/tcp": null,
"80/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8080"
}
]
},
"SandboxKey": "/var/run/docker/netns/0af9e9bf5752",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "33b01b58a9a2ac92011051f1f75aa94ec5eb29bdbac06580aef462d700e527ef",
"EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02"
}
}
}
docker也提供了直接对网络进行管理的docker network
命令,可以通过docker network inspect
命令查看docker网络的详细信息:
# docker network inspect bridge
[
{
"Name": "bridge",
"Id": "33b01b58a9a2ac92011051f1f75aa94ec5eb29bdbac06580aef462d700e527ef",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16"
}
]
},
"Internal": false,
"Containers": {
"81ea762a6bb99c1ee8c72d0fcc91f5151f8f65cf7c26747dd1f64d260845e21c": {
"Name": "nginx",
"EndpointID": "16ab10c9a32ea277f3f78b589ceee0f8698feafdfe6061c9ca3ac9f9633d1af8",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
通过上面可以发现,bridge只不过是ip域为172.17.0.0/16子网,通过ifconfig
命令也可以获得一些信息:
# ifconfig
docker0 Link encap:Ethernet HWaddr 02:42:72:b4:25:d9
inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
inet6 addr: fe80::42:72ff:feb4:25d9/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:37 errors:0 dropped:0 overruns:0 frame:0
TX packets:39 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:3684 (3.6 KB) TX bytes:3399 (3.3 KB)
docker是通过iptables将流量打到docker0的子网上的,因此,可以尝试找下iptables的配置:
# iptables-save |grep 172.17.0.*
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.17.0.2/32 -d 172.17.0.2/32 -p tcp -m tcp --dport 80 -j MASQUERADE
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
-A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 80 -j ACCEPT
在上面已经得知nginx容器的ip正是172.17.0.2
,iptables会将来自8080端口的流量打在80端口上。
其实在宿主机直接访问子网172.17.0.2
的80端口就可以获得nginx正确的返回:
# curl 172.17.0.2:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
容器作为镜像的实例,是镜像中无状态应用的运行时。所谓没有状态,因为容器的生命周期非常灵活。当一个容器的生命周期结束,期间产生的数据不会持久化,而是随着容器的删除而被删除。但是,大多数应用都是为数据服务了,那么,这里就探讨下容器的持久化问题。
在讨论容器的持久化之前,先探索下,没有持久化,也就是一个普通的容器的数据存储是什么样子的。
我们先run一个容器:
# docker run -d ubuntu sleep 100000
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3a8fed62029c ubuntu "sleep 100000" About a minute ago Up About a minute hopeful_kilby
因为容器的本质也就是一个进程,那么,容器内产生的所有的文件肯定也是通过某种方式存储到宿主机上的,我们可以exec进入这个容器,创建一个文件出来:
# docker exec -it 3a8fed62029c bash
root@3a8fed62029c:/# cd home/
root@3a8fed62029c:/home# echo "dao" > hello
root@3a8fed62029c:/home# ls
hello
root@3a8fed62029c:/home# cat hello
dao
创建完成后,可以在/var/lib/docker/aufs/mnt
下找到这个文件:
root@ubuntu:/var/lib/docker/aufs/mnt# find -name hello
./24c7a79f3a3bcb9320e028dab081eec7f55a5a8fb8eb2201d9cfe7b1d9d0e7bf/home/hello
而进入找到这个路径/var/lib/docker/aufs/mnt/24c7a79f3a3bcb9320e028dab081eec7f55a5a8fb8eb2201d9cfe7b1d9d0e7bf
就会发现,这就是容器下的整个文件系统。
如果尝试在宿主机下的这个目录中创建若干文件,则在容器中也同样是可以找到的。
其中的原理也很简单,我们可以在/var/lib/docker/aufs/layers
下找到答案:
root@ubuntu:/var/lib/docker/aufs/layers# cat 24c7a79f3a3bcb9320e028dab081eec7f55a5a8fb8eb2201d9cfe7b1d9d0e7bf
24c7a79f3a3bcb9320e028dab081eec7f55a5a8fb8eb2201d9cfe7b1d9d0e7bf-init
d7b377dff0a5d7b84ae6f30cab5d94b668406e30e8f1584e0eab567b45e27a60
e00588b1d53b6376da8ac07a5176bf44c246a5c1077fd56cf41979565ab8e290
5c05060aac0e7a11db24402fc2639d3fd47dc3ab8a996d279a28ac6d73d1217b
3cba5f639a5bca0a7d7d4b28b68151d8a10101583f1b59f48328ade9f7c668dd
ac5fe0af9b19fe9bb88448932b3c7866e942dbdc9666457195183a2af7caf9f9
正如所知道的,镜像是分层存储的,而容器只不过是以镜像的若干层为基础,在上面有增加了一可读可写层作为数据的存储。
因此,很明白,容器的存储只不过就是把容器内的文件映射到主机的某一个目录上,而这个目录就是这一层可读可写的layer。不过,当容器的生命周期结束,这个宿主机的文件夹也就不复存在。
也因为这个原因,我们需要考虑如何把容器产生的数据持久化。
把容器产生的数据持久化有两个中方法,基本就是两个完全不同的思路。第一种就是通过docker commit,把和容器的生命周期一样的临时的layer持久化:
# docker commit 3a8fed62029c
sha256:c82ef37ba95e8cc6b03a487294385e76d774f46bb225496f9278b50c0137175c
# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> c82ef37ba95e 3 seconds ago 126.6 MB
这样,已经存有数据的layer便永久的存储到新生成的镜像中,想再使用数据时可以通过实例一个新的镜像就可以。
但是,这样的局限性很明显,也更不能满足运行时的需求,因此,我们可以考虑下第二种的持久化方式,Volume。
Volume使用起来也非常简答,在run的时候加一个-v参数就可以了,我们从使用-v参数的三种方式探究下volume。
第一种方式,也就是最简单的,直接挂载一个volume到容器。
我们run一个容器,并挂在一个volume:
# docker run -d -v /volume ubuntu sleep 100000
通过inspect命令,我们可以看到这个容器的volume信息:
"Mounts": [
{
"Name": "8aa697c21e65e104dcb9f8b8507c905715d457ad88ee3c2e79a0c72ef07fff0e",
"Source": "/var/lib/docker/volumes/8aa697c21e65e104dcb9f8b8507c905715d457ad88ee3c2e79a0c72ef07fff0e/_data",
"Destination": "/volume",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
我们可以看到,docker把本地/var/lib/docker/volumes下的一个文件件挂载到了容器的/volume,这个目录上。
当我们再次去/var/lib/docker/aufs/mnt下找打容器对应的目录,并在/volume这个目录下进行修改时,容器里的文件并不会发生什么变化,然而,在/var/lib/docker/volumes/8aa697c21e65e104dcb9f8b8507c905715d457ad88ee3c2e79a0c72ef07fff0e/_data这个目录下进行修改,在容器内就可以看到相应的变化。
但是,这次虽然使用了volume,但是到底和没有使用有什么区别呢?
区别就在于volume这个文件夹并不会和容器一样有相同的生命周期,而是在容器的生命周期结束后保留在宿主机上:
# docker volume ls
DRIVER VOLUME NAME
local 8aa697c21e65e104dcb9f8b8507c905715d457ad88ee3c2e79a0c72ef07fff0e
# docker rm -f f5390aec14bc
f5390aec14bc
# docker volume ls
DRIVER VOLUME NAME
local 8aa697c21e65e104dcb9f8b8507c905715d457ad88ee3c2e79a0c72ef07fff0e
那么这样就满足了对于没有状态的容器,持久化在他生命周期期间产生的数据的问题。不过这样还是有一些不方便,比如不方便管理volume,以及再run一个容器来继承使用前面的容器产生的数据。
而第二种方式便满足了这个问题。第二种方式就是把宿主机的一个目录挂在到容器。
我们可以run一个容器,把本地的/root/data这个目录挂载到容器的/volume下:
# docker run -d -v /root/data:/volume ubuntu sleep 1000000
9548d507859b6a331a167a8f60d6d96ccdf2b0244c6bdd24033ccf277b89d162
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9548d507859b ubuntu "sleep 1000000" 54 seconds ago Up 53 seconds drunk_swanson
我们依然可以通过inspect命令获得volume相关的信息:
"Mounts": [
{
"Source": "/root/data",
"Destination": "/volume",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
这时候就发现,docker十分省事的并没有在volume下创建一个文件夹,而是直接创建了/root/data,然后,直接把这个文件夹挂载到了容器内的/volume。
第三种方式,实例化一个数据卷容器,然后把这个数据卷容器挂载到新建的容器上。
而实例化一个数据卷容器,就有上面提到的两种方式,我们可以分别看下两种不同的数据卷对新创建的容器有什么影响。
我们先run一个容器,并以包含普通volume的容器作为数据卷容器(见上面第一种方式):
# docker run -d --volumes-from f5390aec14bc ubuntu sleep 100000
8cfe0eb466e1935cafe353195deebd0debd677433625d05770b2458755c1a32b
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8cfe0eb466e1 ubuntu "sleep 100000" 8 seconds ago Up 8 seconds evil_nobel
然后,带这个容器进行inspect:
"Mounts": [
{
"Name": "8aa697c21e65e104dcb9f8b8507c905715d457ad88ee3c2e79a0c72ef07fff0e",
"Source": "/var/lib/docker/volumes/8aa697c21e65e104dcb9f8b8507c905715d457ad88ee3c2e79a0c72ef07fff0e/_data",
"Destination": "/volume",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
我们可以看到和前面一样的信息,这个容器并没有创建任何volume,只不过是复用了数据卷容器的volume。
相似的,from一个第二种方式创建的数据卷容器,也会得到类似的结果:
"Mounts": [
{
"Source": "/root/data",
"Destination": "/volume",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
新创建的容器并没有创建volume,依然是直接使用了原有容器的volume,因此,如果多个容器需要共享一个文件目录,完全可以由一个容器创建一个volume,然后其他容器通过volume-from关联这个volume进行使用。
Copyright © 2020 Powered by MWeb, Theme used GitHub CSS.