Docker知识点整理

1.Docker架构

​ 和传统虚拟化相比,省去了Hypervisor层。基于内核的Cgroup和Namespace技术,处理逻辑与内核深度融合,所以在很多方面,它的性能与物理机非常接近。

​ 通信上,Docker不会直接和内核交互,它是通过一个更底层的工具Libcontainer与内核交互的。Libcontainer是真正意义上的容器引擎,通过clone系统调用直接创建容器,通过pivot_root系统屌用进入容器,且同坐直接操作cgroupfs文件实现对资源的管控,而Docker本身则侧重于处理更上层的业务。

2.容器的组成

​ 容器=cgroup+namespace+rootfs+容器引擎(用户态工具)

各功能功能如下:

  • Cgroup:资源控制
  • Namespace:访问隔离
  • rootfs:文件系统隔离
  • 容器引擎:生命周期隔离。

目前市场上所有Liunx容器项目都包含以上组件。

3.容器创建原理

抽象代码如下:

pid = clone(fun, stack, flags, clone_arg);
(flags: CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET|
CLONE_NEWIPC | CLONE_NEWUTS | ...)

注:通过clone系统调用,并传入各个Namespace对应的clone flag,创建了一个新的子进程,该进程拥有了自己的Namespace.

echo $PID > /sys/fs/cgroup/cpu/tasks
echo $PID > /sys/fs/cgroup/cpuset/tasks
echo $PID > /sys/fs/cgroup/blkio/tasks
echo $PID > /sys/fs/cgroup/memory/tasks
echo $PID > /sys/fs/cgroup/devices/tasks
echo $PID > /sys/fs/cgroup/freezer/tasks

注:将代码一产生的进程pid写入各种Cgroup子系统中,这样该进程就可以收到相应Cgroup子系统的控制。

func ()
{
  ...
    pivot_root("path_of_rootfs/", path);
  ...
    exec("/bin/bash");
  ...
}

注:该fun函数由上面生成的新进程执行,在fun函数中,通过pivot_root系统屌用,使进程进入一个新的rootfs,之后通过exec系统调用,在新的Namespace、Cgroup、rootfs中执行"/bin/bash"程序。

通过以上操作,成功地在一个“容器”中运行了一个bash程序。

3.Cgroup介绍

​ Ggroup是 control group的缩写,属于内核提供的特征,用于限制和隔离一组进程对系统资源的使用,做资源Qos,主要包括CPU、内存、block I/O 和网络带宽。

​ Cgroup中实现的子系统及其作用如下:

  • devices: 设备权限控制
  • cpuset: 分配指定的CPU和内存节点
  • cpu: 控制CPU占用率
  • cpuacct:统计CPU使用情况
  • memory:限制内存的使用上限
  • freezer:冻结(暂停)Cgroup中的进程
  • net_cls:配合tc(traffic controller)限制网络带宽
  • net_prio:设置进程的网络流量优先级
  • huge_tlb: 限制HugeTLB的使用
  • pref_event: 允许pref工具基于Cgroup分组做性能监测

3.1Cgroup子系统介绍

3.1.1cpuset子系统

​ cpuset可以为一组进程分配指定的CPU和内存节点。通过将进程绑定到固定的CPU和内存节点上,避免进程在运行时因跨节点内存访问而导致的性能下降。

​ cpuset的主要接口如下:

  • cpuset.cpus: 允许进程使用的CPU列表(例如0~4,9)
  • cpuset.mems: 允许进程使用的内存节点列表(例如0~1)

备注:

​ NUMA结构下, 每个处理器CPU与一个本地内存直接相连, 而不同处理器之前则通过总线进行进一步的连接, 因此相对于任何一个CPU访问本地内存的速度比访问远程内存的速度要快

​ linux内核把物理内存按照CPU节点划分为不同的node, 每个node作为某个cpu节点的本地内存, 而作为其他CPU节点的远程内存, 而UMA结构下, 则任务系统中只存在一个内存node, 这样对于UMA结构来说, 内核把内存当成只有一个内存node节点的伪NUMA

3.1.2cpu子系统

​ cpu子系统用于限制进程的CPU占用率。实际功能如下:

  • CPU比重分配。此特征使用的接口是cpu.shares。假设在cgroupfs的根目录下创建了两个Cgroup(c1和c2),并将cpu.shares分别设置为512和1024.当C1和C2争用CPU时,C2将会比C1得到多一倍的CPU占用率。要注意的是,只有当他们争用CPU时cpu share才会起作用,如果c2空闲,c1可以得到全部资源。
  • CPU带宽限制。此特征使用的接口是cpu.cfs_period_us和 cpu.cfs_quota_us,这两个接口的单位是毫秒。可以将period设置为1s,将quota设置为0.5s,那么Cgroup中的进程在1s内最多运行0.5s,然后就会被强制睡眠,知道进入下一秒。
  • 实时进程的CPU带宽限制。上面两个特征只能限制普通进程,若要限制实时进程,就要使用cpu.rt_period_us和cpu.rt_runtime_us两个接口。方法和上面类似。
3.1.3cpuacct子系统

​ cpuacct子系统用来统计各个Cgroup的CPU使用情况,有如下接口。

  • cpuacct.stat: 报告这个Cgroup分别在用户态和内核态消耗的CPU时间,单位是USER_HZ。USER_HZ在x86上一般是100,即1 USER_HZ等于0.01s。
  • cpuacct.usage: 报告这个Cgroup消耗的总CPU时间,单位纳秒。
  • cpuacct.usage_percpu: 报告这个Cgroup在各个CPU上消耗的CPU时间,总和也就是cpuacct.usage的值。
3.1.4memory子系统

​ memory子系统用来限制Group所能使用的内存上限,有如下接口。

  • memory.limit_in_bytes: 设置内存上限,单位是字节,也可以使用k/K、m/M和g/G。默认情况下,如果Cgroup使用的内存超过上限,Linux内核会尝试回收内存,如果仍无法将内存使用量控制在上限之内,系统将会触发OOM。
    • 例如: echo 1G > memory.limit_in_bytes
  • memory.memsw.limit_in_bytes: 设定内存加上交换分区的使用总量。
  • memory.oom_control: 如果设置为0,那么在内存使用量超过上限时,系统不会杀死进程,而是阻塞进程知道有内存被释放可供使用时;另一方面,系统会向用户态发送事件通知,用户态的监控程序可以根据该事件来做相应的处理,例如提高内存上限。
  • memory.stat: 汇报内存使用信息。
5.1.5blkio子系统

​ blkio子系统用来限制cgroup的block I/O ,有如下接口。

  • blkio.weight: 设置权重值,范围在100~1000之间,跟cpu.shares类似。

  • blkio.weight_deivce: 对具体的设备设置权重值,这个值会覆盖blkio.weight

    • 例如将Group对/dev/vda1的权重设置为最小
    # ls -l /dev/vda1
    brw-rw---- 1 root disk 254, 1 Sep 22  2021 /dev/vda1
    # echo "254:1 100" > blkio.weigth_device
    
  • blkio.throttle.read_bps_device: 对具体的设备,设置每秒读磁盘的带宽上限。

    • 例如对/dev/sda的读带宽限制在1MB/秒; # echo "8:0 1048576" > blkio.throttle.read_bps_device
  • blkio.throttle.write_bps_device: 设置每秒写磁盘的带宽上限。同样需要指定设备。

  • blkio.throttle.read_ips_device: 设置每秒读磁盘的IOPS上限。同样需要指定设备。

  • blkio.throttle.write_ips_device: 设置每秒写磁盘的IOPS上限。同样需要指定设备。

5.5.6 device子系统

​ device子系统用来控制Cgroup的进程对哪些设备有访问权限。有如下接口。

  • devices.list.只读文件,显示目前允许访问的设备列表,每个条目都有三个域,分别为:
    • 类型: 可以是a(设备)、c(字符设备)、或b(快设备)
    • 设备号: 格式为major:minnor 的设备号
    • 权限:rw和m的组合。表示读、写可创建设备结点(mknod)
    • 例子 " a *:* rmw" 表示所有设备都可以访问" c 1:3 r"表示对1:3 这个字符设备即/dev/null只有读权限。
  • devices.allow。只写文件,将上面描述的格式写入改文件,就可以拥有访问权限。
  • devices.deny。 同上

6. Namespace介绍

​ Namespace是将内核的全局资源做封装,使用每个Namespace都有一份独立资源,因为不同的进程在各自的Namespace内对同一种资源的使用不会相互干扰。

​ 目前Linux内核总共实现了6种Namespace:

  • IPC: 隔离System V IPC 和 POSIX消息队列。
  • Network
  • Mount
  • PID
  • UTS: 隔离主机名和域名
  • User: 隔离用户ID和组ID

6.1Namespace的接口和使用

​ Namespace的操作,主要是通过clone、setns和unshare这三个系统调用完成的

​ clone可以用来创建新的Namespace。它接受一个叫flags的参数,这些flag包括 CLONE_NEWS, CLONE_NEWIPC, CLONE_NEWUTS, CLONE_NEWNET, CLONE_NEWUSER,我们通过传入这些CLONE_NEW*来创建新的Namespace。 CLONE_NEWS是用来创建新Mount Namespace的。

​ unshare可以为已有的进程创建新的Namespace。调用这个系统调用的进程,会被放进新创建的Namespace里

​ setns可以将进程放到已有的Namespace里。

6.2 Namespace介绍

6.2.1UTS

​ UTS Namespace用于对主机名和域名进行隔离。使用UTS的原因,主机名可以替代IP地址,也就可以使用主机名在网络上访问某台机器,如果不做隔离,这个机制在容器里会出问题。

6.2.2 IPC

​ IPC是 Inter_Process Communication的缩写,也就是进程间通信。IPC Namespace针对的是System V IPC 和Posix消息队列。这些IPC机制都会用到标识符,例如用标识符来区别不同的消息队列,然后通过标识符找到对应的消息队列进行通信等。

​ IPC Namespace 能做到的事情是,使相同的标识符在两个Namespace中代表不同的消息队列,这样也就使得两个Namespace中的进程不能通过IPC进程通信了。

6.2.3 PID

PID Namespace 用于隔离进程PID号,这样一来,不同的Namespace里的进程PID号就可以是一样的了。

6.2.4 Mount

​ Mount Namespace 用来隔离文件系统挂载点,每个进程能看到的文件系统都记录在/proc/$$/mounts。在创建了一个新的Mount Namespace后,进程系统对文件系统的挂在/卸载的动作就不会影响到其他Namespace.

6.2.5 Network

​ 这个Namespace会对网络相关的系统资源进行隔离,每个Network Namespace都有自己的网络设备、IP地址、路由表、/proc/net目录、端口号等。

​ 新创建的Network Namespace会有一个loopback设备,除此之外不会有任何其他网络设备,因此用户需要在这里面做自己的网络配置。

6.2.6 User

​ User Namespace用来隔离用户和组ID。User Namespace最有用的地方在于,host的普通用户进程在容器里可以是0号用户,也就是root用户。这样,进程在容器里可以做各种特权操作,但是它的特权被限制在容器里。

7.Docker Image

7.1 概念介绍

​ Docker image是用来启动容器的只读模板,是容器启动所需要的rootfs,类似虚拟机的镜像。

​ Docker镜像的表示方法

Remote-dockerhub.com/namespace/Repository:latest
  • Remote docker hub: 集中存储镜像的Web服务器地址。改部分的存在用来区分从不同的镜像库中拉去镜像。若Docker的镜像表示中缺少该部分,说明使用默认的镜像库。
  • Namespace: 类似于GitHub中的命名空间,是一个用户或组织中所有镜像的集合。
  • Repository: 类似Git仓库,一个仓库可以有多个镜像,不同镜像通过tag来区分
  • Tag: 类似Git的tag,区分不同的版本
  • Layer: 镜像由一系列层组成,每层都用64位的十六进制数表示
  • Image ID: 镜像最上层layer ID 就是该镜像的ID。

7.2使用Docker Image

列出本机的镜像
# docker images
列出悬挂镜像
# docker images --filter "dangling=true"
删除悬挂镜像
# docker images --filter "dangling=true" -q |xargs docker rmi

下载镜像
# docker pull busybox

导入镜像(docker load 一般适用于docker save导出的镜像)
docker save -o busybox.tar busybox
docker load -i busybox.tar

增量生成一个镜像
# docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

7.3 Docker image的组织结构

​ Docker image 包含着数据及必要的元数据。数据由一层层的image layer组成,元数据则是一些JSON文件,用来描述数据(image layer)之间的关系和容器的一些配置信息。

7.3.1总体信息
  • /var/lib/docker/image/overlay2:存储镜像管理数据的目录,以使用的存储驱动命名,在我的ubuntu系统上,使用的驱动为overlay2。
  • repositories-overlay文件可以看到该存储目录下的所有image以及对应的layer ID。信息如下。
cat /var/lib/docker/image/overlay2/repositories.json |python -m json.tool
{
    "Repositories": {
        "debian": {
            "debian:10": "sha256:6642e362a8254d7645ed8dae69be5e3edcd2e9a5ee77baeb0655595244082d13",
            "debian@sha256:5b57f8c365c40fde437d53b953c436995525be7c481eb0128b1cbf3b49b0df18": "sha256:6642e362a8254d7645ed8dae69be5e3edcd2e9a5ee77baeb0655595244082d13"
        },
        "registry.cn-hangzhou.aliyuncs.com/ossrs/srs": {
            "registry.cn-hangzhou.aliyuncs.com/ossrs/srs:4": "sha256:9cfcbd2070a489f9e62f6b66fb8f3c106a4b831ce81d4ce49add0b0c34ab39c6",
            "registry.cn-hangzhou.aliyuncs.com/ossrs/srs@sha256:f936cb86d4bf3c31ef319471c1134c5bdf62931235335ac3bb4df2dbe190f1b7": "sha256:9cfcbd2070a489f9e62f6b66fb8f3c106a4b831ce81d4ce49add0b0c34ab39c6"
        }
    }
}
  • /var/lib/docker/image/overlay2/distribution$: 从远端拉到本地的镜像相关元数据
  • /var/lib/docker/image/overlay2/imagedb:镜像数据库
  • /var/lib/docker/image/overlay2/imagedb/content:镜像ID.
  • /var/lib/docker/image/overlay2/layerdb: 镜像每个layer的元数据。
7.3.2 数据的组织
[
    {
        "Id": "sha256:84581e99d807a703c9c03bd1a31cd9621815155ac72a7365fd02311264512656",
        "RepoTags": [
            "nginx:1.7.9"
        ],
        "RepoDigests": [
            "nginx@sha256:e3456c851a152494c3e4ff5fcc26f240206abac0c9d794affb40e0714846c451"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2015-01-27T18:43:46.874407465Z",
        "Container": "a90675014650c505222e83897ffabcc42d43edd9465b4702dd4dc170a8713585",
        "ContainerConfig": {
            "Hostname": "dc534047acbb",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "443/tcp": {},
                "80/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.7.9-1~wheezy"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) CMD [nginx -g daemon off;]"
            ],
            "Image": "e90c322c3a1c8416eb76e6eec8ad2aac7ae2c37b9e6fe6d62cce8224f90e3001",
            "Volumes": {
                "/var/cache/nginx": {}
            },
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": [],
            "Labels": null
        },
        "DockerVersion": "1.4.1",
        "Author": "NGINX Docker Maintainers \"docker-maint@nginx.com\"",
        "Config": {
            "Hostname": "dc534047acbb",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "443/tcp": {},
                "80/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.7.9-1~wheezy"
            ],
            "Cmd": [
                "nginx",
                "-g",
                "daemon off;"
            ],
            "Image": "e90c322c3a1c8416eb76e6eec8ad2aac7ae2c37b9e6fe6d62cce8224f90e3001",
            "Volumes": {
                "/var/cache/nginx": {}
            },
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": [],
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 91664166,
        "VirtualSize": 91664166,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/23f1fb53a3fc3ae631226f6ed1e1bf818ab1b69e1211826d0be590ca2ae32dae/diff:/var/lib/docker/overlay2/9e766a89d2463b9aa2b2992ec384479b41c3caceef6da8f2bdd32dc2b46e8392/diff:/var/lib/docker/overlay2/68987914ca6e8b569f125680b7ea613f7afece4fcb6ab57908f6773cb8bd4540/diff:/var/lib/docker/overlay2/8ae0ce80fc63fc8ce6363ce01ebaaafbe1aa22a2ec694b006518d9c932300a74/diff:/var/lib/docker/overlay2/3d4f899e8ba4e0fd0a8939996f478e47b2035818b3cf8e77e76798af795d966a/diff:/var/lib/docker/overlay2/8e6f646f4208f9d8f92ad11701113866926bf86e5d2f0ffb5d1468ab55e68e16/diff:/var/lib/docker/overlay2/c66c5303a281b450fdba3ec4d15adb3c2df0923726604cfd9b180311ba7be1d3/diff:/var/lib/docker/overlay2/4edb12e0b836ad7432d9b4ff4764860be0137ef2158e424f3cba9108433fe1a8/diff:/var/lib/docker/overlay2/e0286810bb3d8ecfb6805c8448fbd13bfa8628a838b6afd09f33fb50bb5c5e1f/diff:/var/lib/docker/overlay2/9ca3926b529bfd2acc708de1b32f247e8084764397d9be84da4495227524f8f4/diff:/var/lib/docker/overlay2/9e4153054116b4816d0735ab9934ffc47509227fa0bb8bdb6e63909882112c06/diff:/var/lib/docker/overlay2/aa301becf97cb3ca7d998d7673a3eab2d3d9987921ea80ab97aef9be88305e73/diff",
                "MergedDir": "/var/lib/docker/overlay2/b70ccb1704354eb7b16c3eab884210693dfa05db39cc51c1177d65c082fd37f9/merged",
                "UpperDir": "/var/lib/docker/overlay2/b70ccb1704354eb7b16c3eab884210693dfa05db39cc51c1177d65c082fd37f9/diff",
                "WorkDir": "/var/lib/docker/overlay2/b70ccb1704354eb7b16c3eab884210693dfa05db39cc51c1177d65c082fd37f9/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
                "sha256:dea2e4984e295e43392ea576c8159168eb877c117714940abf3af9b604e874c2",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
                "sha256:e02dce553481aaff0458344505cb6adc66c2714755af96a4234463c7374be46a",
                "sha256:63bf84221cce5ef5920911ded704f3bf363f6024929d3b8c9aa31a4b0c7fb46a",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
                "sha256:e387107e2065a133384512ef47c36a2c1a31961310468b3c3eae00f4da47f2c1",
                "sha256:ccb1d68e3fb76bc27e1745f004ba389489b56d00f350c6e93f7cc5b8baa03309",
                "sha256:4b26ab29a475a42c35ee78b4153a67eaa9ef33e7c9e1c7d1f22c113a71b71604",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
                "sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

输出的解释:

  • Id :Image 的ID (最上层layer ID)
  • Parent: 改layer的父层,可以递归获取某个image的所有layer信息。
  • Comment: 可以为该层做一些历史记录
  • Container:容器的启动需要以image为模板。但又可以把容器保存为镜像,所以一般来说image的layer都保存自一个容易,所以改容器可以说是image layer的模板
  • Config: 包含了该Image的一些配置信息
  • Architecture: 该image对应的CPU架构

8.Docker网络

​ Libnetwork 提出了新的容器网络模型(Container Network Model, CNM),定义了标准的API用于为容器配置网络,其底层可以适配多种网络驱动,CNM有三个重要的概念:

  • 沙盒。沙盒是一个隔离的网络运行环境,保存了容器网络栈的配置,包括了对网络接口、路由表和DNS配置的管理。Linux平台是用 Linux Network Namespace实现的。一个沙盒可以包括来自多个网络的多个Endpoint(端点)。

  • Endpoint。Endpoint将沙盒加入一个网络,Endpoint的实现可以是一堆veth pair或者OVS内部端口,当前的Libnetwork使用的是veth pair。一个Endpoint只能隶属于一个沙盒及一个网络。通过给沙盒增加多个Endpoint可以将一个沙盒加入多个网络。

  • 网络。网络包括一组能互相通信的Endpoint.网络的实现可以是Linux bridge、vlan等。

    image-20220417103828374

​ 从CNM的概念角度看,Libnetwork的出现使得Docker具备了跨主机多子网的能力,同一个子网内的不同容器可以运行在不同的主机上。目前Libnetwork实现了五种驱动:

  • bridge:Docker默认的容器网络驱动。Container 通过一对veth pair连接到docker0网桥上,由Docker为容器动态分配IP及配置路由、防火墙规则等。
  • host: 容器与主机共享同一Network Namespace,同享同一套网络协议栈、路由表以及iptables规则等。容器与主机看到的是相同的网络试图
  • nuill: 容器内网络配置为空,需要用户手动为容器配置网络接口及路由等。
  • remote: Docker网络插件的实现。Remote driver使用Libnetwork可以通过HTTP RESTful API对接第三方的网络方案。
  • overlay: Docker原生的跨主机多子网网络方案。主要通过使用Linux bredge和vxlan隧道实现,底层通过类似于etcd或consul的KV存储系统实现多机的信息同步。

8.1网络配置

​ 在Linux平台下,Docker容器网络资源通过内核的Network Namespace实现隔离,不同的Network Namespace有各自的网络设备、协议栈、路由表、防火墙规则等。同一Network Namespace下的进程共享同一网络试图。

  • none: 不为容器配置任何网络功能。

    • 在改模式下,需要以--net=none参数启动容器,容器启动之后,仍然可以手动为容器配置网络。
  • Container: 与另一个运行中的容器共享Network Namespace,共享相同的网络试图。

    • 添加参数--net=container: ContainerID
  • host: 与主机共享Root Network Namespace,容器有完整的权限可以操纵主机的协议栈、路由表和防火墙等,所以被认为是不眼圈的

    • 添加参数--net=host
  • bridge: Docker设置的NAT网络类型

    • Docker daemon启动时会在主机创建一个Linux网桥(默认为docker0)。容器启动是,Docker会创建一对veth pair(虚拟网络接口)设置,veth设备的特点是成对存在,从一端进入的数据会同时出现在另一端。Docker会将一段挂载到docker0网桥上,另一端放入容器的Network Namespace内,从而实现容器与主机通信的目的。

    • image-20220417113546647

    • 在桥接模式下,Docker容器与Internet的通信,以及不同容器之间的通信,都是通过iptables规则控制的。Docker网络的初始化动作包括:创建docker0网桥、为docker0网桥新建子网及路由、创建相应的iptables规则等。

    • #默认docker0分配了172.17.42.1/16的子网,容器以bridge网络模式运行时默认从这个子网分配IP,这个IP和阿里云分配的IP冲突,我更换了一个。
      root@jiangfeng-Debian-Server:~# ip addr show docker0
      3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
          link/ether 02:42:b9:74:10:a8 brd ff:ff:ff:ff:ff:ff
          inet 192.168.1.5/24 brd 192.168.1.255 scope global docker0
             valid_lft forever preferred_lft forever
          inet6 fe80::42:b9ff:fe74:10a8/64 scope link
             valid_lft forever preferred_lft forever
      
      #docker会写入路由表规则
      root@jiangfeng-Debian-Server:~# route -n
      Kernel IP routing table
      Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
      0.0.0.0         172.19.63.253   0.0.0.0         UG    0      0        0 eth0
      172.19.0.0      0.0.0.0         255.255.192.0   U     0      0        0 eth0
      192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 docker0
      
      #以bridge模式启动的容器,默认会从你的bridge子网内分配IP
      root@jiangfeng-Debian-Server:~# docker run -it --net=bridge debian:10 ip addr show
      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
      66: eth0@if67: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
          link/ether 02:42:c0:a8:01:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
          inet 192.168.1.2/24 brd 192.168.1.255 scope global eth0
             valid_lft forever preferred_lft forever
      
  • overlay: Docker原生的跨主机多子网模式。overlay底层需要蕾西consul活etcd的KV存储系统进行消息同步,核心是通过Linux网桥与vxlan隧道实现跨主机划分子网。

image-20220417115813641

8.2Docker网络参数

8.2.1Docker daemon
Docker daemon部分的网络参数说明如下:
$docker daemon--help
A self- sufficient  runtime for linux  containers . 
Options:
-b,--bridge=				Attach  containers  to a network bridge
# 指定Docker daemon使用的网桥,默认为“docker0"。若设置-b="none”,则禁用Docker的网络功能
--hip=							Specify network bridge IP
#指定docker0网桥的IP,注意--bip不能与-b同时使用
--default-gateway=		Container default gateway IPv4 address
#设置容器默认的IPv4网关
--default-gateway-v6=	Container default gateway IPv6 address
#设置容器默认的IPv6网关
--dns=[]							DNS server to use
#设置容器内的DNS服务器地址
--dns-search=[]DNS search domains to use
#设置容器内的search domain,即域名解析时默认添加的域名后缀
--fixed-cidr=IPv4 subnet for fixed IPs
#容器网络模式为bridge时,会从此子网内分配IP,本参数设置的子网必须嵌套于docker0网桥所属子网之内
--fixed-cidr-v6=			IPv6 subnet for fixed IPs
#与--fixed-cidr相同,bridge模式下默认分配的IPv6子网地址
-H,--host=[]					Daemon socket(s) to connect to
#指定Docker client与Docker daemon通信的socket地址,可以是tcp地址、unix socket地址. w或socket文件描述符,可同时指定多个,例如:
# docker daemon-H tcp://10.110.48.32:10000 -H unix:///var/run/docker.sock
#代表Docker daemon同时监听10.110.48.32;10000及本机/var/run/docker. sock文件

--icc-true					Enable inter-container  communication
# 允许/禁止容器间通信,禁用icc依赖iptables规则,若--icc-false,则必须--iptables=true
ip=0.0.0.0 					Default IP when binding container ports
#容器暴露端口时默认绑定的主机IP,默认为0.0.0.0
--ip-forward=true 	Enable net.ipv4. ip forward
#使能IP转发功能,为true则向主机/proc/sys/net/ipv4/ip forward写入1
--ip-masq-true 			Enable IP  masquerading
#使能IP地址变形功能(NAT),只有--iptables=true才可生效
--iptables=true 		Enable addition of iptables rules
#使能iptables。若设置为false,则无法向iptables表格添加规则
--ipv6=false 				Enable IPv6  networking
#使能IPv6网络功能
--mtu-0 						Set the containers network MTU
#设置容器网络MTU(最大传输单元)
--userland-proxy=true Use userland proxy for loopback traffic
#非当设置为true时,Docker会为每个映射到主机端口的容器端口启动一个docker-proxy进程用于数据转发。实际上这会耗用大量CPU资源,Docker社区正在计划去除docker-proxy

8.2.2Docker clinet
$ docker run --help
Usage:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
Run a command in a new container

--add-host list                  Add a custom host-to-IP mapping (host:ip)
#在容器内的/etc/hosts文件内增加一行主机对IP的映射
--dns list                       Set custom DNS servers
#设置容器的DNS服务器
--expose list                    Expose a port or a range of ports
#暴露容器的端口,而不映射到主机端口
-h, --hostname string                Container host name
#设置容器的主机名
--link list                      Add link to another container
#链接到另一个容器,在本容器中可以通过容器ID或者容器名访问对方
--mac-address string             Container MAC address (e.g., 92:d0:c6:0a:29:33)
#设置容器的mac地址
--net=bridge
#这是容器的网络运行模式,当前支持四种模式:bridge、none、host、container
-P,--publish-all=false                    Publish all exposed ports to random ports
#将容器所有暴露出来的端口映射到主机随机端口
-p, --publish list                   Publish a container's port(s) to the host
#将容器一段范围内的端口映射到主机指定的端口

两个示例:

docker daemon -H tcp://10.110.52.38:6000 -H unix:///var/run/docker.sock --fixed-cidr=172.17.55.0/24 --icc=false --userland-proxy=false

docker run -itd --net=bridge -p 10000:22/tcp -h docker ubuntu:latest bash

9 Docker 卷管理

​ Docker容器里产生的数据,如果不通过docker commit 生成新的镜像,使数据作为镜像的一部分保存下来,就会在容器删除后丢失。为了能够持久化保存和共享容器的数据,Docker 提出了卷(volume)的概念。

​ 卷就是目录或文件,由Docker daemon挂在到容器中,不属于联合文件系统,卷中的数据在容器被删除后仍然可以访问。Docker提供了两种管理数据的方式: 数据卷和数据卷容器。

增加新数据卷

#增加新数据卷
$ docker run -v /tmp/data busybox
-v 参数会在容器的/tmp/data目录下创建一个新的数据卷,docker inspect 命令可以查看数据卷在主机中的位置
#将主机的文件或文件夹挂在到容器中
$ docker run -v /host/data:/data busybox
-v 参数的主机目录必须使用绝对路径,如果指定路径不存在,DOcker会自动创建该目录
#只读方式挂在数据卷
$ docker run -v /host/data:/data:ro busybox

创建数据卷容器

# 创建多个Postgres
$ docker create -v /dbdata --name dbdata training/postgres /bin/true
$ docker run -d --volumes-from dbdata --name db1 training/postgres

10 Docker API

配置

$ vim /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd  -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375
$ systemctl daemon-reload
$ systemctl restart docker

运行容器

package main

import (
	"context"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/docker/pkg/stdcopy"
	"io"
	"os"
)

func main() {
	ctx := context.Background()
	//初始化一个新的 API 客户端
	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(),client.WithHost("tcp://121.43.167.3:2375"))
	if err != nil {
		panic(err)
	}
	//ImagePull 请求 docker 主机从远程注册表中提取图像。如果操作未经授权,它会执行特权功能并再试一次。
	reader, err := cli.ImagePull(ctx, "docker.io/library/alpine",types.ImagePullOptions{})
	io.Copy(os.Stdout, reader)
	resp, err := cli.ContainerCreate(ctx, &container.Config{
		Image: "alpine",
		Cmd:   []string{"echo", "helloworld"},
	}, nil, nil,nil, "")
	if err != nil {
		panic(err)
	}
	//ContainerStart sends a request to the docker daemon to start a container.
	if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil{
		panic(err)
	}
	//ContainerWait等待指定的容器处于给定条件指示的特定状态,即“未运行”(默认)、“下一个退出”或“已删除”。
	statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
	select {
	case err := <-errCh:
		if err != nil{
			panic(err)
		}
		case <- statusCh:
	}

	out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true, Timestamps: true})
	if err != nil {
		panic(err)
	}
	stdcopy.StdCopy(os.Stdout, os.Stderr, out)

}

参考文档:

https://pkg.go.dev/github.com/docker/docker/client#section-readme

https://www.jb51.net/article/208930.htm

注意:开放公网端口有被攻击的风险

posted @ 2022-05-05 09:39  better_feng  阅读(505)  评论(0编辑  收藏  举报