概述

基础概念

  1. docker是一种容器化技术,通过Namespaces技术来实现资源隔离。Linux Namespaces主要提供了6种资源的隔离机制(其他还有Cgroup等)

    • Mount: 隔离文件系统挂载点
    • UTS: 隔离主机名和域名信息
    • IPC: 隔离进程间通信
    • PID: 隔离进程的ID
    • Network: 隔离网络资源
    • User: 隔离用户和用户组的ID
    sudo /proc/pid/ns # 可查进程的namespaces情况
     
    cgroup -> cgroup:[4026531835]
    ipc -> ipc:[4026531839]
    mnt -> mnt:[4026531840]
    net -> net:[4026531992]
    pid -> pid:[4026531836]
    pid_for_children -> pid:[4026531836]
    time -> time:[4026531834]
    time_for_children -> time:[4026531834]
    user -> user:[4026531837]
    uts -> uts:[4026531838]
     
    # 可以看到每个资源都对应的有一个id,这个id就是对应的namespace,一般的docker容器进程只有time,time_for_children,user资源是不隔离的

    在clone()进程时,可以指定要隔离的内容,隔离哪个,在哪个维度你就只能看到相关命名空间里的内容,比如隔离pid,在pid维度,你无法看到其他命名空间的pid,但是其他没有隔离的资源依然共享,比如time…

    相同命名空间则资源共享,docker容器的隔离就是这么实现的。

    例子:

    run 一个 mysql

    5105fee6f9f5   mysql:8.0   "docker-entrypoint.s…"   4 hours ago   Up 4 hours   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   condescending_khorana
     

    通过pstree或者docker top containerid 查看得知,run命令之后,容器在宿主机上主要有2个进程,bash和mysqld(run的时候有bash进程,如果是start container 则只有一个mysqld进程)

    systemd+            29352               29332               0                   00:22               ?                   00:00:02            mysqld
    root                29681               29332               0                   00:28               pts/0               00:00:00            /bin/bash
     

    查看29352的namespaces

    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 cgroup -> 'cgroup:[4026531835]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 ipc -> 'ipc:[4026532606]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 mnt -> 'mnt:[4026532604]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:56 net -> 'net:[4026532609]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 pid -> 'pid:[4026532607]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 pid_for_children -> 'pid:[4026532607]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 time -> 'time:[4026531834]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 time_for_children -> 'time:[4026531834]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 user -> 'user:[4026531837]'
    lrwxrwxrwx. 1 systemd-coredump input 0 6月  25 20:57 uts -> 'uts:[4026532605]'
     

    查看29681的namespaces,发现和mysqld相同

    同时mysqld下面开启的其他子进程,都拥有和mysqld同样的namespaces

    通过查看cat /proc/29352/status | grep NSpid可以看到mysqld进程在容器内是1号进程。

    NSpid:	29352	1

    正常系统的1号进程肯定是系统进程,但是容器内的1号进程就是一个普通进程,由Entrypoint指定。

    所以,run一个容器,实际就是开了一个新的命名空间,然后将相关进程加入这个命名空间。从宿主机的视角,这些进程都是运行在宿主机上的,只是命名空间不同,从命名空间里的视角来看,内部只有内部的视角,看不到宿主机,以为自己是个独立的系统,所以还能产生pid=1的进程

    另:容器的真实载体是一个叫containerd-shim的进程,这个进程是宿主机的进程,没有开启命名空间(或者说和宿主机一个命名空间),他会产生容器进程,每启动一个容器都会产生一个containerd-shim进程。

  2. docker使用文件分层技术,一个完整的镜像由多个层组成,每一层可以复用。

    文件分层的好处是,如果多个镜像都使用到了一个文件层,那么这个文件层只会下载一个,被共享了。

  3. docker daemon

    docker相对于podman有2个特点,root运行,必须要有daemon程序。

    docker的daemon程序为整个核心,所有服务由其提供

    命令行工具实际是调用docker Remote API和deamon交互,最终实现操作。

    为什么是 Remote API,这不是本地调用吗?

    实际上docker在运行时采用C/S模型,daemon为服务端,docker CLI相当于客户端,daemon通过Unix socket监听请求,一切功能的实现实际在服务端完成(所以docker 很容易就能实现远程调用,daemon可以部署在其他主机上)。比如通过dockfile构建的镜像的时候,build的最后一个参数为上下文路径,build命令会将上下文路径里的所有内容全部传输给daemon,以完成构建。

image分层

分层原理

每一个image实际由多层组成。

以一个image的构建为例:

FROM ubuntu:18.04
COPY . /app

由2个命令组成,每个命令就是一层layer

构建过程:

Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM ubuntu:18.04
 ---> 7d0d8fa37224
Step 2/2 : COPY . /app
 ---> ae9e62f2be44
Successfully built ae9e62f2be44
Successfully tagged acme/my-base-image:1.0

查看history:

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
ae9e62f2be44   34 minutes ago   /bin/sh -c #(nop) COPY dir:d526a01dff55d760c…   161B      
7d0d8fa37224   13 days ago      /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      13 days ago      /bin/sh -c #(nop) ADD file:900f735ff138e5137…   63.1MB 

<missing>表示这部分是其他系统构建的,你不具有回滚操作能力。FROM unbuntu:18.04这是一个基础镜像,他的构建由发布者完成。所以本次构建实际从CMD ["bash"]开始,可以理解为这是一个FROM启动命令。

然后直接COPY复制文件。

**综上:**这个镜像将由2个layer组成。


以上述镜像为基础再构建一个镜像:

# 上述镜像acme/my-base-image:1.0
FROM acme/my-base-image:1.0
CMD /app/hello.sh

构建过程:

Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM acme/my-base-image:1.0
 ---> ae9e62f2be44
Step 2/2 : CMD /app/hello.sh
 ---> Running in bd5c62ac9b5a
Removing intermediate container bd5c62ac9b5a
 ---> f4f02e2c32f0
Successfully built f4f02e2c32f0
Successfully tagged acme/my-final-image:1.0

从dockfile中可以看出,还是2步,构建过程也是2步

查看history:

IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
f4f02e2c32f0   28 minutes ago   /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/app…   0B        
ae9e62f2be44   29 minutes ago   /bin/sh -c #(nop) COPY dir:d526a01dff55d760c…   161B      
7d0d8fa37224   13 days ago      /bin/sh -c #(nop)  CMD ["bash"]                 0B        
<missing>      13 days ago      /bin/sh -c #(nop) ADD file:900f735ff138e5137…   63.1MB   

由于FROM的镜像由本机构建,所以这里能看到基础镜像的构建过程

FROM对应了基础镜像的2个layer + <missing>

然后新增了一个layer

**综上:**此镜像由2步构建,分为3层,和基础镜像共用了2层,最终文件系统里会新增一层数据。

Layer存储在/var/lib/docker/<storage driver>

storage driver目前推荐使用的是overlay2。

docker info可以查看现在使用的storage driver。

image大小

可以通过docker ps -s查看容器镜像的大小

  • size: 可写层的大小
  • virtual: image + 可写成的大小

所以image的大小 = virtual - size

如果一个image构建了n个容器,那么总磁盘占用量的大小应是:

n * size + (virtual -size)

因为每个容器都有独立的容器层(可写层),但是image层是共用的。

注意:以上占用量并不包括volumn和一些配置文件,这些内容是在host上的,不计算进容器内。

容器层逻辑

COPY-ON-WRITE

镜像是由多个分层文件组成,在文件层最上面加一个可写层,镜像就变成了容器。

镜像是只读的,因为可写层,容器是可修改的。

如果要修改“镜像中”的文件,怎么实现?

通过copy-on-write来完成:

当容器需要读取文件的时候

从最上层镜像开始查找,往下找,找到文件后读取并放入内存,若已经在内存中了,直接使用。(即,同一台机器上运行的docker容器共享运行时相同的文件)。

当容器需要添加文件的时候

直接在最上面的容器层可写层添加文件,不会影响镜像层。

当容器需要修改文件的时候

从上往下层寻找文件,找到后,复制到容器可写层,然后,对容器来说,可以看到的是容器层的这个文件,看不到镜像层里的文件。容器在容器层修改这个文件。

当容器需要删除文件的时候

从上往下层寻找文件,找到后在容器中记录删除。即,并不会真正的删除文件,而是软删除。这将导致镜像体积只会增加,不会减少。

由以上可知:

只有当文件需要编辑的时候,才会读入到可写层,所以可写层的内容实际非常少。

理解:当容器中的进程要读取文件时,可以看作是一个俯视的视角,同一个东西只能看到最上面的,一个文件修改后,他会存在于位于最上层的可写层中了,进程只能看到最上面那一个,所以修改就生效了,这也是为什么容器层要放在最上面的缘故。

安装

  1. 卸载旧版本

     sudo yum remove docker \
                      docker-client \
                      docker-client-latest \
                      docker-common \
                      docker-latest \
                      docker-latest-logrotate \
                      docker-logrotate \
                      docker-engine
  2. 安装必要的系统工具

    sudo yum install -y yum-utils device-mapper-persistent-data lvm2
    # device-mapper-persistent-data lvm2 用于持久化存储的,是docker使用逻辑卷技术的必要工具
    # 实际现在已推荐使用overlay2,这是默认设置,不需要安装额外的软件了
  3. 安装docker-ce等内容

    yum install --allowerasing docker-ce docker-ce-cli containerd.io # 由于centos8内置podman,和docker冲突,需要添加--allowerasing 选项
  4. 配置docker仓库,使用阿里云的源

    sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

    配置专属镜像加速

    获取加速地址

    sudo mkdir -p /etc/docker
    sudo tee /etc/docker/daemon.json <<-'EOF'
    {
      "registry-mirrors": ["https://ivzo68z7.mirror.aliyuncs.com"]
    }
    EOF
    sudo systemctl daemon-reload # 重载配置文件
    sudo systemctl restart docker # 重启
  5. 查看

    docker --version
    systemctl start docker # 启动

卸载

sudo yum remove docker-ce docker-ce-cli containerd.io
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd

rootless docker vs root docker

rootless下docker daemon以普通用户运行,杜绝了绝大部分问题,但是可能会遇到2类问题:

  1. 容器无法启动,有些容器需要特殊的权限
  2. 多网卡情况,可能导致无法从所有网卡访问到

Note

获取不到真实ip,这个可以配置来解决

如果不是安全性要求太高,建议暂时(docker v29.3.0)还是以root模式安装,但是最好开启userns-remap,这样容器内的root用户会映射到host的普通用户,避免过高的权限,同时应该配置一些安全选项,比如cap_drop,以及资源限制。

Tip

关于映射规则 可查看/etc/subuid和/etc/subgid, 一般使用userns-remap: default后,在这2个文件会有个dockremap: 100000:65536的规则,意思是可以映射从100000到165536的uid。 映射规则为: container uid + 100000 host uid 假设容器内的用户id为0,101,经过映射后,则为100000和100101

root模式下的userns-remap和rootless都利用了用户态空间的原理,但是实现不同,userns-remap只是一个简单的特性,且只在root模式下有。

Caution

在有些tun模式代理下,userns-remap可能造成容器间的访问问题。

Caution

tun模式下也会造成外部访问容器的问题,需要排除掉docker相关的网卡。

权限问题

不管是root还是rootless,都要注意文件权限,特别是挂载已有目录时,要明白对应的host用户是哪个才能chown,否则很可能看到各种无权限的错误。

docker proxy

使用proxy pull image,由于docker是cs结构,cli本质上只是一个客户端,操作都是由docker daemon完成,所以需要daemon本身能够识别proxy。

有2种模式:

  1. daemon.json

    OS and configurationFile location
    Linux, regular setup/etc/docker/daemon.json
    Linux, rootless mode~/.config/docker/daemon.json
    WindowsC:\ProgramData\docker\config\daemon.json
    {
      "proxies": {
        "http-proxy": "http://proxy.example.com:3128",
        "https-proxy": "https://proxy.example.com:3129",
        "no-proxy": "*.test.example.com,.example.org,127.0.0.0/8"
      }
    }
    # restart service
    sudo systemctl restart docker
  2. environment variables

    rootless mode: ~/.config/systemd//docker.service.d/

    In addition, systemctl must be executed without sudo and with the --user flag.

    regular: /etc/systemd/system/docker.service.d

    # create config file
    sudo mkdir -p /etc/systemd/system/docker.service.d
    vim /etc/systemd/system/docker.service.d/http-proxy.conf
    # add below lines
    [Service]
    Environment="HTTP_PROXY=http://proxy.example.com:3128"
    Environment="HTTPS_PROXY=https://proxy.example.com:3129"
    Environment="NO_PROXY=localhost,127.0.0.1,docker-registry.example.com,.corp"
    # reload
    sudo systemctl daemon-reload
    sudo systemctl restart docker
    # verify
    sudo systemctl show --property=Environment docker

docker cli

busybox

为了方便测试,使用这个工具箱镜像,封装了几百个linux常用命令,很好用。

docker run -it busybox --name test1

veth-pair

当我们使用bridge默认模式,使用busybox开启2个容器

此时主机上新增了3个设备

#ip link
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default 
    link/ether 02:42:33:8a:9b:c7 brd ff:ff:ff:ff:ff:ff
15: veth4c0cf57@if14: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
    link/ether b2:3b:30:86:24:a7 brd ff:ff:ff:ff:ff:ff link-netnsid 1
17: vethb2ff952@if16: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT group default 
    link/ether a6:43:72:73:2f:39 brd ff:ff:ff:ff:ff:ff link-netnsid 2

其中:

  • docker0,为docker daemon创建的一个虚拟网桥,可以简单理解为交换机,这个交换机用于各个容器的通信。正常交换机是没有IP的,现实中使用交换机是将一根网线插上面,而这里是虚拟的交换机,主机怎么识别呢,答案是分配一个IP,这个IP就是主机连接虚拟交换机的IP,也是网关地址。(完全可以把这个IP当作主机在这个网段的IP)

  • veth4c0cf57@if14和vethb2ff952@if16是虚拟网卡,进入busybox 容器,能看到2个容器的网卡

    14: eth0@if15: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
        link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
     
    16: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue 
        link/ether 02:42:ac:11:00:04 brd ff:ff:ff:ff:ff:ff

    观察网卡前面的数字以及@if后面的数字,14 — 15,16 —17,这4个网卡实际是2对。一个在主机上,一个在容器内部。这就是veth-pair技术,总是成对出现,veth-pair就是一个桥梁、一根网线,连接2端,可以**穿透network namespace**,让2端通信,一端发出的数据包可以直接到达另一端。

    再观察主机上veth4c0cf57@if14和vethb2ff952@if16虚拟网卡:

    # nmcli -p d show
    GENERAL.DEVICE:                         docker0
    GENERAL.TYPE:                           bridge
    GENERAL.HWADDR:                         02:42:33:8A:9B:C7
    GENERAL.MTU:                            1500
    GENERAL.STATE:                          100(连接(外部))
    GENERAL.CONNECTION:                     docker0
    GENERAL.CON-PATH:                       /org/freedesktop/NetworkManager/ActiveConnection/5
    IP4.ADDRESS[1]:                         172.17.0.1/16
    IP4.GATEWAY:                            --
    IP4.ROUTE[1]:                           dst = 172.17.0.0/16, nh = 0.0.0.0, mt = 0
    IP6.ADDRESS[1]:                         fe80::42:33ff:fe8a:9bc7/64
    IP6.GATEWAY:                            --
    IP6.ROUTE[1]:                           dst = fe80::/64, nh = ::, mt = 256
    IP6.ROUTE[2]:                           dst = ff00::/8, nh = ::, mt = 256, table=255
     
    GENERAL.DEVICE:                         veth4c0cf57
    GENERAL.TYPE:                           ethernet
    GENERAL.HWADDR:                         B2:3B:30:86:24:A7
    GENERAL.MTU:                            1500
    GENERAL.STATE:                          10(未托管)
    GENERAL.CONNECTION:                     --
    GENERAL.CON-PATH:                       --
    WIRED-PROPERTIES.CARRIER:
     
    GENERAL.DEVICE:                         vethb2ff952
    GENERAL.TYPE:                           ethernet
    GENERAL.HWADDR:                         A6:43:72:73:2F:39
    GENERAL.MTU:                            1500
    GENERAL.STATE:                          10(未托管)
    GENERAL.CONNECTION:                     --
    GENERAL.CON-PATH:                       --
    WIRED-PROPERTIES.CARRIER:

    他们是没有ip的,为什么?查看状态也是未连接的!

    # nmcli -p d status
    DEVICE       TYPE      STATE         CONNECTION 
    ens33        ethernet  已连接        ens33      
    docker0      bridge    连接(外部)  docker0    
    veth4c0cf57  ethernet  未托管        --         
    vethb2ff952  ethernet  未托管        --         
    lo           loopback  未托管        --    

    实际上,此时这2个虚拟网卡被添加到了docker0虚拟交换机上,变成了虚拟交换机的端口,端口是没有IP的。相当于网线一头插主机,一头插交换机,插主机上的网卡有IP地址,插交换机上的很显然没必要有。

    # 安装bridge-utils,需要epel源
    yum install -y bridge-utils
    # brctl show docker0 
    bridge name	bridge id		STP enabled	interfaces
    docker0		8000.0242338a9bc7	no		veth4c0cf57
    										vethb2ff952
    # 可知这2个网卡都添加到了docker0上
    brctl addif docker0 vetchxxx # 可以将设备添加到交换机上

    至此:

    容器/主机之间的网络通信完全连通。容器怎么连外网呢?通过NAT转发,容器 交换机 网关 NAT转发。

    实际交换机和网关就是docker0。

    查看HOST中docker0的NAT规则

    #iptables -t nat -S | grep docker0
    -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE

    可知:docker0如果收到来自172.17.0.0/16网段的请求,会交给MASQUERADE处理,而MASQUERADE处理方式是将包的源地址替换成HOST的地址再发出去,做了一次网络地址转换(NAT)。

    举例:

    # 容器ping外网
    [wgx@localhost ~]$ sudo docker exec -it test1 ping www.baidu.com
    PING www.baidu.com (14.215.177.39): 56 data bytes
    64 bytes from 14.215.177.39: seq=0 ttl=127 time=23.789 ms
    64 bytes from 14.215.177.39: seq=1 ttl=127 time=22.918 ms
     
    # 监听主机虚拟网桥
    [root@localhost ~]# tcpdump -i br-702eb58eabcf -n icmp
    dropped privs to tcpdump
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on br-702eb58eabcf, link-type EN10MB (Ethernet), capture size 262144 bytes
    09:17:04.601609 IP 172.18.1.1 > 14.215.177.39: ICMP echo request, id 15, seq 0, length 64
    09:17:04.625245 IP 14.215.177.39 > 172.18.1.1: ICMP echo reply, id 15, seq 0, length 64
    09:17:05.602734 IP 172.18.1.1 > 14.215.177.39: ICMP echo request, id 15, seq 1, length 64
    09:17:05.625344 IP 14.215.177.39 > 172.18.1.1: ICMP echo reply, id 15, seq 1, length 64
     
    # 监听主机网卡
    [root@localhost ~]# tcpdump -i ens33 -n icmp
    dropped privs to tcpdump
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
    09:17:55.745305 IP 192.168.227.100 > 14.215.177.39: ICMP echo request, id 15, seq 51, length 64
    09:17:55.768962 IP 14.215.177.39 > 192.168.227.100: ICMP echo reply, id 15, seq 51, length 64
    09:17:56.748906 IP 192.168.227.100 > 14.215.177.39: ICMP echo request, id 15, seq 52, length 64
    09:17:56.772987 IP 14.215.177.39 > 192.168.227.100: ICMP echo reply, id 15, seq 52, length 64

    可以看到 172.18.1.1容器IP地址,在ping外网时,被NAT转换成了192.168.227.100,也就是HOST的IP地址。

    正常都是容器端口映射到主机端口,然后外网访问主机端口,完成对容器的访问。

    如果想外网直接访问容器也可以做到(非host模式):

    1. 创建虚拟网桥,分配IP地址,IP地址为主机所在网络
    2. 创建veth-pair,一个连接上述网桥,一个分配给容器
    3. 给容器内的veth分配IP地址,此时和网桥同一网段,实际就是和主机同一网段了,外网能直接访问主机,就能直接访问容器,因为同一网段了

    实际就是类似vmware桥接模式(将一个物理网卡开启混合模式,能监听多个IP),虚拟机和主机在一个网段,处于同一层。


网络总结:

docker0或者说bridge网络模式下在host上创建的虚拟设备,这个设备被host识别(也就是适配了,在相关维度,宿主机访问其他设备的代表就是这个设备)。模型可以想象成下面,网卡-网桥-网关就是docker0。

host适配了这个设备,设别了这个网卡,拥有了地址。

容器连接上了网桥,此时容器,host通过网桥完成互相通信。

如果要访问外网,会经过网关,网关进行NAT转换,转换为Host的可用IP地址,然后根据这个IP地址转发到对应的网卡上,从而完成外网的访问。

graph LR
1[host]--适配-->2[网卡]
2---3[网桥]
3---4[网关]
5[容器]--连接-->3

引申内容,vmware的网络模式。

vmware有4种网络模式:

  1. none,没有网络,相当于一台没有网卡的设备,即不能访问主机,也不能访问外网。

  2. bridge,将host的物理网卡设置成混合模式,能监听多个ip,像网桥一样接入多个设备,此时虚拟机和host在同一层。

  3. host-only,虚拟机只能访问host,无法访问外网

  4. nat模式

    这种模式整体上就是创建一个子网,然后通过nat转发访问外网。

    vmware会创建一个虚拟交换机,虚拟DHCP服务器,虚拟网关(NAT服务器),并在host上创建一个虚拟网卡vmnet8。

    graph TB
    1[虚拟交换机] 
    2[虚拟DHCP服务器] -->1
    3["虚拟网关(NAT)"] ---1
    h["host vmnet8"] -->1
    V1[VM1] -->1
    V2[VM2] -->1
    Vn[VM..] -->1
    p["host 物理网卡"] ---3
    
    • DHCP: 自动分配虚拟机IP,可以关掉

    • 虚拟机之间可以通过交换机通信

    • 由于vmnet8的存在,host和虚拟机之间可以通过虚拟交换机通信

    • 虚拟机如果要访问外网,可以通过网关进行NAT转换,转换为host的ip,进而通过物理网卡访问外网

    虚拟机和vmnet8的网关地址就是虚拟网关的地址。但是host是ping不通这个网关地址的,尽管他们在同一个网段,原因不明(可能是如果host能到达这个网关,那host访问外网是不是也要nat转发一次,然后又转到自身的物理网卡上,画蛇添足?)。

    从上图也能看到,虚拟机访问外网是通过网关完成,并没有经过vmnet8,所以就算禁用了vmnet8也不影响虚拟机访问外网,只是不能访问host。

    实际上nat模式和host-only模式唯一的区别就是,host-only没有虚拟网关这个设备(host-only下在host上创建的虚拟网卡一般命名为vmnet1),所以无法访问外网。

docker run

基本命令

$ docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]

选项

  • Detached : -d,背景运行,类似 command &

  • Foreground: 默认行为,前端运行,就是将容器的stdin,stdout,stderr附加到控制台,也就是终端上

    -a=[]           : Attach to `STDIN`, `STDOUT` and/or `STDERR` # -a=[stdout,stderr]默认行为
    -t              : Allocate a pseudo-tty # 分配一个伪tty
    --sig-proxy=true: Proxy all received signals to the process (non-TTY mode only)
    -i              : Keep STDIN open even if not attached # 保持输入流打开,即使没有连接到

    一般-it组合使用,才能激活终端、输入流

    如果要在-d模式下运行,并且确保命令执行后不会退出容器

    docker run -d --rm --name test busybox /bin/sh # 执行了sh后没有新的任务,容器自动退出了
    docker run -dit --rm --name test busybox /bin/sh # 执行了sh后,由于分配了终端,也没有关闭stdin,容器还在等待输入,此时并不会关闭容器
  • 容器命名: 容器在运行时如果没有通过--name指定一个名称,docker daemon会生成一个UUID,并随即生成一个name

    docker run -it --name test busybox /bin/sh
  • 指定镜像

    docker run ubuntu:14.04 # 指定版本,不指定,默认最新版本

    如果要更精确的指定,可以指定摘要

    $ docker run alpine@sha256:9cacb71397b640eca97488cf08582ae4e4068513101088e9f96c9814bfda95e0 date
  • 设置PID namespace

    --pid=""  : Set the PID (Process) Namespace mode for the container,
                 'container:<name|id>': joins another container's PID namespace # 指定其他容器的
                 'host': use the host's PID namespace inside the container # 和主机共享
  • network: 可以使用的网络模式有:

    • none: 不允许容器有任何网络连接,别人也无法连接过来

    • bridge: 桥接模式,默认模式

    • host: 使用宿主机的网络

      相当于不开启network namespace(共用主机网卡)

    • container: 和其他容器共享网络

      和其他容器用一个network namespace(共用其他容器网卡)

    • 自定义网络: 可以使用docker network driver自建网络,并将容器加入其中

      $ docker network create -d bridge my-net
      $ docker run --network=my-net -itd --name=container3 busybox

    还可以指定dns,默认和主机的dns相同;指定IP;指定host;指定mac地址。

    --dns=[]           : Set custom dns servers for the container
    --network="bridge" : Connect a container to a network
                          'bridge': create a network stack on the default Docker bridge
                          'none': no networking
                          'container:<name|id>': reuse another container's network stack
                          'host': use the Docker host network stack
                          '<network-name>|<network-id>': connect to a user-defined network
    --network-alias=[] : Add network-scoped alias for the container
    --add-host=""      : Add a line to /etc/hosts (host:IP)
    --mac-address=""   : Sets the container's Ethernet device's MAC address
    --ip=""            : Sets the container's Ethernet device's IPv4 address
  • 重启策略:

    docker run --restart=on-failure:10 redis # on-failure策略,最多10次
    PolicyResult
    no不自动重启
    on-failure[:max-retries]除非正常退出,也就是exit(0),会一直被daemon重启,可以设置最大重启次数,否则将一直重启
    always一直重启,不管退出状态是什么
    unless-stopped一直重启,除非容器正常stop

    关于退出状态:

    0表示正常退出

    非0表示异常,比如:

    [root@any ~]# docker exec -it 66c2a929553d /bin/bash;echo $?
    OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "/bin/bash": stat /bin/bash: no such file or directory: unknown
    126
    # 退出number 126,没有/bin/bash这个目录(busybox的shell是sh)
  • 退出容器时删除

    正常退出容器,容器内包括匿名卷都会保留,如果仅仅是临时测试,退出容器后想清除相关数据

    --rm # 退出容器后会自动删除容器
  • 容器资源约束

    很多内容,可以约束内存,比如最小内存多少,最大内存多少,swap多少等等,默认是不约束,要多少给多少

    还可以约束cpu,内核内存,硬盘约束等等

    比如:

    $ docker run -it -m 300M ubuntu:14.04 /bin/bash # 内存300M,Swap300M,一共600M限制
  • 特权容器

    正常容器无法访问任何设备,但是可以给容器授权,让容器和在宿主机上运行的进程权限几乎一样

    OptionDescription
    --cap-addAdd Linux capabilities
    --cap-dropDrop Linux capabilities
    --privilegedGive extended privileges to this container
    --device=[]Allows you to run devices inside the container without the —privileged flag.

    privileged相当于容器中的普通用户提升为真正的root用户

    可以通过—device限定访问哪些设备,但是遗憾的是通过—device指定后,依然访问不了,需要进一步授权

    • 通过privileged提升为root权限

      docker run -it --rm --name test3 --device=/dev/sda5 --privileged  busybox /bin/sh
      # 现在可以进行mount了,但是--privileged权限太大,很危险
    • 其他方式--cap-add,添加其他权限

      docker run -it --rm --name test3 --device=/dev/sda5 --cap-add=SYS_ADMIN  busybox /bin/sh
      # 添加SYS_ADMIN权限,可以进行挂载/dev/sda5
    • 如果仅仅是要或的设备的read,write,mknod权限

      docker run -it --rm --name test3 --device=/dev/sda5:rwm  busybox /bin/sh
      # 默认是可以rwm的,可以手动控制
  • 日志驱动

    默认使用json-file,还有一些其他指定。

  • Dockfile

    通过dockfile启动的容器,可以在run的时候指定参数覆盖dockerfile中的参数

    但是FROM, MAINTAINER, RUN, 和ADD的值是不能覆盖的。

    下面的属性可在run的时候覆盖:

    • CMD

       # 可以使用COMMAND替换dockfile中的CMD指定的指令
       docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...]
    • ENTRYPOINT

      ENTRYPOINT是容器启动时默认执行的,可以看作init(1),1号进程。

      实际上ENTRYPOINT和CMD很像,如果没有指定ENTRYPOINT会去执行CMD作为init,如果2者同时存在,CMD会组合进ENTRYPOINT的指令中,当作选项或参数

      docker run -it --entrypoint /bin/bash example/redis -c ls -l # COMMAND作为了entrypoint执行文件,也就是/bin/bash的参数

      甚至可以直接重置掉ENTRYPOINT

      docker run --entrypoint="" busybox /bin/sh
    • EXPOSE

      暴露端口

      --expose=[]: 暴露容器一个、一组或一段范围端口
      -P         : 把暴露出去的端口映射到Host的随机端口上
      -p=[]      : 精确映射一个端口或一段范围端口,format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort;-p 1234-1256:1234-1256/tcp
      --link=""  : Add link to another container (<name or id>:alias or <name or id>)

      关于--link

      docker run -it --rm --expose 10080  --name test3 busybox /bin/sh # test3容器
      docker run -it --rm --link test3:busytest3 --name test4 busybox /bin/sh # test4容器

      test4 link test3并给被连接对象test3指定别名busytest3

      此时:

      • 查看test4中的/etc/hosts

        172.17.0.4	busytest3 1791fb88ae3a test3 # 此时test4拥有test3的容器name,容器id,以及别名设置的host
      • 查看env

        BUSYTEST3_NAME=/test4/busytest3 # 获得Alias_NAME的环境变量
      • test3暴露端口10080,test4环境变量中同样有记录

        BUSYTEST3_PORT_10080_TCP_ADDR=172.17.0.4
        BUSYTEST3_PORT_10080_TCP_PORT=10080
        BUSYTEST3_PORT_10080_TCP_PROTO=tcp
        BUSYTEST3_PORT=tcp://172.17.0.4:10080
        BUSYTEST3_PORT_10080_TCP=tcp://172.17.0.4:10080

      当使用--link时,连接容器和被连接容器之间通过私有网络接口连通了,可以通过域名直接访问,如果暴露了端口,同样可以访问这些端口(暴露端口仅仅是声明本机暴露了相关端口,如果能访问本机,就能访问这个端口,但是并没有和host端口进行映射,也就是访问host时转发不到容器中)。

    • ENV

      Linux docker默认会生成一些环境变量:

      VariableValue
      HOMESet based on the value of USER
      HOSTNAMEThe hostname associated with the container
      PATHIncludes popular directories, such as /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
      TERMxterm if the container is allocated a pseudo-TTY
      export TODAY=WEN
      docker run -it --rm --name test5 -e TODAY -e "VAR=123" busybox /bin/sh 
      # 指定环境变量,可使用当前的环境变量,或者完全自定义
    • HEALTHCHECK

      健康检查,可以在运行容器时,使用健康检查,指定检查命令,检查间隔,失败次数等,开启了健康检查的容器,在docker ps也会有相关状态。

      初始状态:starting

      健康:healthy

      不健康:unhealthy

        --health-cmd            检查命令,容器内执行的命令
        --health-interval       检查间隔
        --health-retries        尝试次数
        --health-timeout        超时时间
        --health-start-period   开始检查的等待时间
        --no-healthcheck        不开启健康检查
    • TMPFS

      tmpfs是一个在内存中的虚拟文件系统,一般默认都支持,可以通过df -h查看

      挂载临时文件系统命令: mount -t tmpfs -o size=20m tmpfs /tmp

      通过—tmpfs可以挂载容器内目录到tmpfs

       docker run -it --rm --tmpfs /tmp:size=20m --name test6 busybox /bin/sh
       # 将一个20M的tmpfs挂载到/tmp目录上了
    • VOLUME

      容器持久化存储技术:

      容器中目录和host目录的关联:官方称这种映射关联为挂载,实际也可以理解为挂载,把host目录、文件、tmpfs作为一个文件系统挂载到容器中的目录上。

      主要有3种方式,指定容器卷挂载到容器内目录,指定host文件系统(文件或目录)挂载进容器内目录,挂载tmpfs,容器卷是可被docker直接管理的,但是host文件系统是被host管理。

      3种方式.img

      从图上可以很直观的3种方式的区别,bind对应host 文件系统的内容(理论上docker daemon host上的所有文件、目录都可以挂载进容器),volume对应docker管理区域(/var/lib/docker/volumes),tmpfs mount将tmpfs(内存中)挂载到容器中目录。

      实际可以看出bind mount已经超出了docker的管理范围,可能对其他非相关文件,进程等产生影响,所以非必要建议不要使用。

      1. volume方式

        容器卷是独立管理,也就是可以提前创建,同时容器的存在与否并不影响容器卷的状态(匿名卷在docker rm -v container模式下会随着容器的删除而删除)。一个volume可以被挂载到多个容器上。

        docker volumn create my_vol # 创建一个具名容器卷
        docker volumn ls # 查看
        docker volumn inspect my_vol
        docker volumn rm my_vol # 删除

        容器卷可以在run的时候创建,此时可以是具名的或匿名的,匿名的由docker daemon指定一个唯一的名字。

        docker run -d --name test -v my_vol:/tmp:ro busybox /bin/sh # volumn_name:dest:option ,dest必须是容器中目录,option可以设置只读ro,读写rw,这个权限是针对卷的。如果省略volumn_name会创建一个匿名卷。my_vol并不需要提前创建,docker daemon可以自动以这个名字创建具名卷。
      2. bind mount方式

        mount的方式,重点在type:

        • volumn: 此时和容器卷的表现一致
        • bind: bind模式,可以挂载host上任意文件和目录
        • tmpfs: 挂载tmpfs文件系统
        docker run -d --name test --mount type=[volumn|bind|tmpfs] , source=/...,target=/...,readonly,volume-opt=xxx
        # type默认是volumn
        # source/src,如果type是volumn,那这里可以指定目录,或容器卷
        # target/dst,容器内的目录
        # readonly只读模式
        # volume-opt 可以指定很多选项,可以指定多个
        # 以上选项都用逗号隔开,无顺序要求
      3. tmpfs挂载

        可以使用--tmpfs,也可以通过上面的--mount type=tmpfs来指定

        docker run -it --mount type=tmpfs,target=/tmp,tmpfs-size=20m,tmpfs-mode=1770 busybox /bin/sh
        docerk run -it --tmpfs:size=20m /tmp:
      4. --volumes-from: 把其他容器挂载的内容引用过来,相当于其他容器挂载的目录和容器卷,原样挂载进当前容器

        docker run -it --rm --name test3 -v /tmp/mydata busybox /bin/sh
        docker run -it --rm --name test4 --volumes-from test3 busybox /bin/sh
        # 此时test4,出现/tmp/mydata,并被挂载了相同的容器卷

        可以很方便的进行备份

         docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
         # 比如dbstore中的dbdata,备份进/backup,相当于备份进了当前目录

      bind propagation:

      这是一个嵌套挂载的数据传播属性,比如/mnt 挂载到/tmp上,然后/foo挂载到/tmp/a上,默认/tmp/a下面的内容也是会传播到/mnt里的。

      这是一个高级选项,一般很少需要去修改。

      关于容器无法删除错误:

      提示容器无法删除,因为Device or resource busy,这时候可以通过lsof找到使用这个资源的进程

      ==注意:==

      1. 如果挂载的volume为空目录,则容器中的对应目录里的内容会copy进volume。(-v 模式下可以设置option,nocopy阻止容器内的内容在挂载时自动复制进volume中。bind模式下不支持修改)
      2. 如果挂载的文件系统或volume为非空目录,且容器中对应的目录不为空,那么和正常的挂载一样,容器中目录下的内容会被隐藏(不是删除),取消挂载后就能看到。
    • USER

      通过-u选项可以覆盖dockerfile中指定的USER,指定的用户如果是数值,会当作uid,且这个uid不需要再容器中提前存在,如果指定的是name,那必须在容器中存在。这个用户会去执行第一个process。

      [ user | user:group | uid | uid:gid | user:gid | uid:group ] # 可以同时指定group
    • WORKDIR

      工作目录,实际就是进去后所在目录

      -w可以进行覆盖。

docker attach

获取一个容器的stdin,stdout,stderr,实际就是进入这个容器

docker attach test1

docker build

根据dockerfile构建image

虽然可以使用远程地址,比如Git构建,但是一般都是使用本地文件构建。

docker build [options] [path] <context> # 最后的context是构建的上下文,默认会去这个context下找Dockerfile文件,path是相当于上下文的路径
docker build . # 当前目录是context,去当前目录下找Dockerfile
# 指定当前目录下的dockerfiles文件夹下的内容,必须要用-f指定
docker build -f dockerfiles/Dockerfile.base .
docker build -f dockerfiles/Dockerfile.prov .
docker build -f dockerfiles/Dockerfile.dev .
docker build -t myapp:1.0 . # -t 指定tag

如果一个Dockerfile有多个阶段,比如多个FROM,可以通过--target指定以哪一个作为final阶段

FROM debian AS build-env
...
 
FROM alpine AS production-env
...
docker build -t mybuildimage --target build-env .

成功构建后删除中间容器

docker build --rm .
docker builder prune # 删除所有的中间缓存

docker commit

将一个容器生成镜像

docker commit [options] CONTAINER IMAGENAME:TAG

提交期间容器是暂停的,可以通过-p false不暂停

-m: 添加说明

-a: 添加作者

-c: 改变配置,比如当前容器的镜像指定的CMD,EXPOSE等

docker container

相关命令同docker command

docker cp

docker cp [options] SRC_PATH CONTAINER:DEST_PATH  # 将宿主机中的文件复制进容器中 -a 选项可以复制UID/GID等信息
docker cp [options] CONTAINER:SRC_PATH DEST_PATH # 将容器中的文件复制进宿主机

docker diff

docker diff CONTAINER # 容器修改了哪些内容
# A 表示add
# D 表示delete
# C 表示change

docker create

和docker run的选项参数相同,docker run实际是先create再run

docker create ...
docker start container

start容器会去执行 docker ps 中显示的command,也就是docker create/run指定的entrypoint cmd,或者是dockfile中指定的ENTRYPOINT CMD

docker exec

在主机中执行容器命令

docker exec CONTAINER COMMAND # command必须是可执行的

可以-e设置环境变量,-w设置WORKDIR

docker exec -it -e VAR=1 -w /tmp test1 /bin/sh # 开启一个新的bash,可以看到pid不是1,此时exit并不会kill容器

docker export

将容器的文件系统导出

docker export -o test1.tar test1

dokcer history

查看image的history

docker history [options] IMAGE
docker history --no-trunc f4f02e2c32f0 # 完成的输出,不进行截断
 
IMAGE                                                                     CREATED       CREATED BY                                                                                             SIZE      COMMENT
sha256:f4f02e2c32f07d015faee4464cc1ba5543c15e6d7d3df71dfb2e59d05f12c730   6 hours ago   /bin/sh -c #(nop)  CMD ["/bin/sh" "-c" "/app/hello.sh"]                                                0B        
sha256:ae9e62f2be44feba8a4232d4072ae40c276907cf8b6bcf34673eff977cb37851   6 hours ago   /bin/sh -c #(nop) COPY dir:d526a01dff55d760ca6fbc6c95bb81b42cbaffe3bfe06bc24513a4a98eae1f64 in /app    161B      
sha256:7d0d8fa372249c6a1de9868ad62af9a14aaae2a2b17da867d8fad099a637fd0f   13 days ago   /bin/sh -c #(nop)  CMD ["bash"]                                                                        0B        
<missing> 
docker history -q  f4f02e2c32f0 # 只显示ID
docker history -H f4f02e2c32f0 # 更易读的方式显示
docker history --format "{{.ID}}:{{.CreatedSince}}:{{.Size}}"  f4f02e2c32f0 # 格式化输出

可以使用的格式:

PlaceholderDescription
.IDImage ID
.CreatedSinceElapsed time since the image was created if --human=true, otherwise timestamp of when image was created
.CreatedAtTimestamp of when image was created
.CreatedByCommand that was used to create the image
.SizeImage disk size
.CommentComment for image

docker image

  • docker image prune -a: 移除所有未被使用的image
  • docker image ls: 列出所有image
  • docker image rm: 删除image

docker info

docker info # 展示当前docker实例的整体信息

docker inspect

以json格式展示docker objects的详细信息

可以给容器、镜像、Volume、Network等使用

docker inspect test1 # 查看这个容器
docker inspect my-net # 查看network
docker inspect busybox # 查看image

docker kill

docker kill -s 1 test1 # 发送Sign指令,常用的有 1,9,15

docker load

docker load < busybox.tar.gz # 实际就是加载本地image包,让其被docker纳入管理
docker load -i busybox.tar.gz # 效果同上

docker logs

docker logs test1 # 查看某个容器的日志

docker network

  • create

    docker network create [OPTIONS] NETWORK

    指定驱动,如果是单主机使用bridge,如果是多主机且容器在一个子网里,那必须使用overlay

    docker network create -d bridge my-net # 注意my-net的命名,首先必须是唯一的,第二这个名字并不是host中虚拟网桥的名字,会自动生成一个,一般是br-xxxxx

    其他常用选项

    docker network create \
    -d bridge \ 
    --subnet 172.18.0.0/16 \  # 子网范围
    --ip-range 172.18.5.0/25 \ # 自动分配ip的范围,/25,表示后面的主机号只有7位了,能分配0-128。
    --gateway 172.18.5.1 \ # 网关地址,也就是虚拟网桥的地址,如果不指定,会自动生成
    br0
    # docker run --network br0 --ip 这里指定的IP只要是subnet中的IP即可,并不一定要在ip-range中,但是建议符合ip-range
  • connect

    docker network connect [OPTIONS] NETWORK CONTAINER

    容器在启动时可以通过--network my-net指定子网,对于一个启动了的容器,可以通过connect连接

    docker network connect \
    --ip 172.18.5.2 # 指定ip
    --alias tt # 在这个网络中给容器分配一个别名
    br0 # 指定network
    test3 # 容器name/id
  • disconnect

    docker network disconnect br0 test3 # -f可以强制断开,注意容器必须是运行中的
  • ls

    docker network ls # 展示所有子网
  • prune rm

    docker network prune # 删除所有未被使用的
    docker network rm my-net # 删除指定的network

docker start stop restart pause unpause

docker COMMAND CONTAINER # 开启、停止、重启、暂停、取消暂停容器,可以一次操作多个容器

docker port

docker port CONTAINER # 展示容器expose的端口以及映射关系

docker ps

docker ps \ # 展示容器
-a # 展示所有状态的
--no-trunc # 不进行截断
-s # 展示size
--filter # 过滤

docker pull

docker pull mysql:5.7.0 # pull image

docker rename

docker rename test1 test-one # 重命名容器

docker rm

docker rm test1 # 删除容器
docker rm -v test1 # 删除容器的同时删除匿名卷
docker rm --link /webapp/redis # 删除基于docker0的--link,这里没有删除容器

docker rmi

docker rmi IMAGE # 删除镜像

docker save

docker save busybox > busybox.tar # 将镜像打包,默认是打包到stdout
docker save busybox | gzip > busybox.tar.gz # 打包压缩
docker search nginx # 从docker hub中搜索镜像

docker stats

docker stats  \ # 动态的展示容器资源使用情况
--no-stream \ # 只展示一次
test1

结果:

CONTAINER ID   NAME      CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O   PIDS
6a7399899179   test1     0.00%     1.086MiB / 1.748GiB   0.06%     12.1kB / 8.33kB   0B / 0B     2
# CPU % 占主机CPU资源比例
# MEM % 占主机内存
# MEM USAGE / LIMIT 容器允许使用的内存,和已经使用的内存
# NET I/O 网络IO
# BLOCK I/O 硬盘IO
# PIDS 容器有多少进程

docker system

  • docker system df: 查看docker磁盘使用情况

  • docker system prune: 删除所有未被使用的镜像、容器、卷、网络等docker对象。

    \
    -a # 移除所有
    --volumes # 移除卷

docker top

docker top test1 # 展示这个容器的进程,注意这里展示的进程是映射到主机的进程,毕竟容器的进程也是跑在主机上,所以容器内的进程在主机上肯定有对应的进程

docker update

可以修改资源限制,比如内存限制

docker update -m 500M test1

可以修改restart策略

docker update --restart on:failure:3 test1

dockerfile

FROM

FROM一般是第一条指令,用于指定基础image。

ARG是唯一一个可以放在FROM之前的指令,可以指定变量,然后FROM可以使用,但是FROM之后的指令无法使用,可以在FROM后面定制ARG。

ARG VERSION=latest

FROM busybox:$VERSION

RUN

ENTRYPOINT和CMD是构建后在容器中运行的指令

而RUN是构建的时候运行的指令,RUN运行的结果可以在后面的指令中应用

RUN有2种模式:

  • RUN <command>: 命令默认使用/bin/sh这个shell。

    RUN echo "hello" # /bin/sh -c echo "hello"
    RUN /bin/bash -c echo "hello" # /bin/sh -c /bin/bash -c echo "hello"
  • RUN [“executable”, “param1”, “param2”]

    RUN ["/bin/bash","-c","echo hello"] # 更灵活一点,字符串拼接难度降低。并且可以不在shell中执行

CMD

有3种模式

  • CMD command param1 param2: shell模式,同样的默认使用/bin/sh -c 去执行。(如果ENTRYPOINT存在,那么会以/bin/sh -c param1 param2的形式添加在ENTRYPOINT后面)

    CMD echo "hello" # /bin/sh -c echo "hello"
  • CMD [“executable”,“param1”,“param2”]: 类似上面的RUN exec form,可以直接执行指定executable,而不需要在shell中执行。

    CMD ["/usr/bin/wc","--help"]
  • CMD [“param1”,“param2”]: 作为ENTRYPOINT的参数

注意:RUN可以有很多个,但是CMD只能有一个,如果有多个只有最后一个生效。

不在shell中执行,在容器中很重要,因为如果在shell中执行,那么shell就是pid=1进程了。很多指令都可以不用在shell中执行

LABLE

以key=value的格式设置标签,可以设置多个,空格用反斜杠转义,或者使用双引号包裹

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."
# 以上也可以放在一行中

EXPOSE

暴露端口,注意这里是暴露,并不是publish,只是expose给同一network的其他应用。默认是TCP,可以指定

EXPOSE 80/tcp # 真正的publish需要在run的时候设置,这里实际就是告诉使用镜像的人,你可以映射这个端口

ENV

设置环境变量,这里设置的变量可以在dockerfile后面的指令中使用,并且会在容器中存在

ENV TODYA=wen # 同样的空格需要用双引号或者反斜杠转义
# 可以多个ENV,也可以放在一行

如果仅仅只是想在build的时候有效,可以使用ARG

ADD

将基于context中的文件或目录添加进容器

src文件基于context相对定位

dest可以基于WORKDIR相对定位,也可以是用绝对定位

ADD 可以添加tar包,会自动解压

src可以有多个,此时dest必须以/结尾

如果dest不存在会自动创建。(mkdir -p)

COPY

对比ADD,src不能是URL和tar,其他基本相似。

能用COPY的地方就不要用ADD

ENTRYPOINT

和CMD一样,有shellform 和 execform2种格式

shellform会在/bin/sh中执行,并且CMD设置的内容和run命令中指定的参数全部失效。同时由于是在sh这个shell中执行,那么进程必然是这个shell的子进程,意味者容器的pid=1进程不是期望的进程,而是shell,期望的进程就无法接收SIGNAL,比如docker stop CONTAINER时,期望的进程无法收到信号(都是发给1号进程的),可能造成困惑和问题。

而execform可以避免这些问题,可以追加CMD和run中的参数,同时直接执行executable,让其成为1号进程。

FROM ubuntu
ENTRYPOINT ["top", "-b"] # 直接执行top命令,查看得知top此时就是pid=1
CMD ["-c"]

多个ENTRYPOINT只有最后一个生效。

VOLUME

VOLUME ["/data"]
VOLUME /data /tmp

Dockerfile无法指定命名卷,这很正常,如果image中指定了命名卷,意味着所有基于这个image的容器都有相同的卷,数据就乱了。

WORKDIR

可以指定多次,如果后面指定的是相对路径,那么就是相对之前的路径

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd # 此时在/a/b/c

WORKDIR影响很大,ENTRYPOINT,CMD,RUN在该目录下运行,COPY,AND如果是相对路径,则是这个目录的相对路径。

ARG

ARG user=w # 指定默认值
USER $user # 使用
# ARG只在被定义后才能使用,且只在定义所在的stage生效
docker build --build-arg user=www . # 构建的时候指定

ARG总是会被同名的ENV覆盖

FROM ubuntu
ARG CONT_IMG_VER
ENV CONT_IMG_VER=v1.0.0
RUN echo $CONT_IMG_VER # 不管build --build-arg指定了没有,这里的结果必然是v1.0.0

ONBUILD

把一个image作为一个基础image时,这个基础image中定义的ONBUILD会在以这个镜像作为基础镜像构建镜像时调用,调用时机在FROM后面。

# base image
FROM centos
ONBUILD ADD . /tmp
ONBUILD RUN ["/bin/bash","-c","echo hello"]
ENTRYPOINT ["/bin/bash"]
# extend image
FROM base-img
ENTRYPOINT ["/bin/bash"]
# build extend image
Sending build context to Docker daemon  4.096kB
Step 1/2 : FROM base-img
# Executing 2 build triggers 2个触发器被触发了
 ---> Running in f106ded8eca0
hello  # 输出了结果,进入容器可以看到ADD命令的结果
Removing intermediate container f106ded8eca0
 ---> cf0db7d2d1f5
Step 2/2 : ENTRYPOINT ["/bin/bash"]
 ---> Running in da69b6f32841
Removing intermediate container da69b6f32841
 ---> 226b35122d8c
Successfully built 226b35122d8c
Successfully tagged img-extend:latest

SHELL

Dockerfile中的指令如果使用的shell form格式,默认调用的就是["/bin/sh","-c"]这个shell,可以通过SHELL重新指定。

SHELL可以出现多次,每一次都会覆盖之前指定的,并影响后面的所有命令。

Compose

安装

直接下载二进制文件

sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
# 速度较慢可以 -x socks5://代理地址

修改权限

sudo chmod a+x /usr/local/bin/docker-compose

查看版本

docker-compose --version

卸载

直接删除下载的那个文件即可

sudo rm /usr/local/bin/docker-compose

compose cli

类似docker cli管理镜像、容器、网络、dockerfile等,compose cli也可以基于docker-compose.yml对整个应用进行管理

命令格式:

docker-compose [options] [COMMAND] # 基本格式

options

-f

默认会去当前文件夹下寻找docker-compose.yml文件,可以通过-f指定,指定后出现的相对路径都是以这个指定的文件路径为参照。

docker-compose -f ~/docker-compose.yml # 指定yml文件
docker-compose -f ~/docker-compose.yml pull db # 根据这个yml的配置,获取db服务的镜像
-p

默认会以当前目录名为project-name(在生成比如network,容器名称时会自动添加project-name前缀)

可以-p指定一个名称

—profile

给service添加profiles标记后,可以通过命令行工具让这些标记enabled,从而启用对应的服务

docker-compose --profile debug --profile dev up

环境变量

实际上即可以通过-f,-p等设定内容,也可以使用内置的环境变量,来设置相关内容。可以理解为按以下顺序配置:

  1. 根据option进行设置
  2. 根据环境变量进行设置
  3. 使用默认值

COMPOSE_PROJECT_NAME:

# pwd /root/myapp
export COMPOSE_PROJECT_NAME=Mall;docker-compose up # 此时会以Mall而不是myapp作为项目名称

COMPOSE_FILE:

同样的,可以指定yml文件的位置,可以指定多个用:隔开即可

export COMPOSE_FILE=/root/myapp/docker-compose.dev.yml:/path1/path2/docker-compose.yml

还有其他变量可以设置。

COMMAND

bash中基于docker-compose的命令补全功能较弱,可以添加额外的补全配置。

 sudo curl \
    -L https://raw.githubusercontent.com/docker/compose/1.29.2/contrib/completion/bash/docker-compose \
    -o /usr/local/etc/bash_completion.d/docker-compose
 source ~/.bashrc # 重新加载配置文件,或者关掉终端,然后重新连接

另:

命令一般有2种模式

  • 针对service,这和docker command基本一样
  • 针对整个应用(命令不体现任何service),相当于针对整个yml配置文件管理的内容
build
docker-compose build [SERVICE...] # build image。如果某个service的dockerfile改变了,可以通过build指令重新build,否则会使用之前build的image
config

用于查看yml文件,这里会替换各种变量,生成最终需要的内容

docker-compose config
down

up用于构建,down就是删除,停止容器并删除相关内容

docker-compose down # 默认删除创建的容器,网络。external的容器卷、网络不会被删除
docker-compose down --rmi {all|local} -v # all删除所有image,local删除没有image属性的image,-v删除具名卷,service里的匿名卷
exec
docker-compose exec SERVICE /bin/sh # 和docker exec相同
images
docker-compose images # 基于这个yml所构建的image ls
kill
docker-compose kill -s 1|9|15 [SERVICE...] # 默认是9,直接杀掉
logs
docker-compose logs --tail 10 [SERVICE...] # 查看日志
pause,restart,start,stop,unpause
docker-compose start [SERVICE...] # 类似docker cli里的指令
ps
docker-compose ps # 展示这个应用所有启动中的容器情况
# -a 查看所有
# --services 只展示service
pull
docker-compose pull [SERVICE...] # 获取某个服务的image,如果是dockerfile形式,使用build命令
# --includes-deps 包括依赖一起pull
rm
docker-compose rm [SERVICE...] # 删除停止的容器
# -f 强制删除
# -s 删除之前先停止
# -v 同时删除匿名卷
run

单独运行一个service,注意这里会基于yml的配置运行一个新的容器。run可以使用的选项和docker run类似。

docker-compose run [options] SERVICE [COMMAND] [ARGS...]

options基本和docker run 相同,比如

  • -d: 后台运行

  • —name: 指定容器名称

  • —entrypoint: 覆盖yml、Dockerfile或者image中的entrypoint

  • -e: 环境变量

  • —rm: 一次性

  • -p: 端口映射。

    注意:run命令不会发布service中指定的端口,以免端口冲突,如果要使用service中的端口定义,添加--service-ports选项。并且这个选项和-p手动添加的端口映射不能共存。

  • -v: 容器卷

  • -w: 指定workdir

  • —no-deps: 默认会去检查depends_on的服务是否开启,没有开启则启动,然后再run这个容器,可以使用这个选项关掉依赖检查,不去管依赖服务是否启动。

top
docker-compose top [SERVICE...] # 类似docker top CONTAINER
up

这是一个复合指令,类似docker run,整合了build/pull,create,start,attach等过程,相当于直接通过yml开启容器。

Usage: up [options] [--scale SERVICE=NUM...] [SERVICE...]
 
Options:
    -d, --detach               Detached mode: Run containers in the background,
                               print new container names. Incompatible with
                               --abort-on-container-exit.
    --no-color                 Produce monochrome output.
    --quiet-pull               Pull without printing progress information
    --no-deps                  Don't start linked services.
    --force-recreate           Recreate containers even if their configuration
                               and image haven't changed.
    --always-recreate-deps     Recreate dependent containers.
                               Incompatible with --no-recreate.
    --no-recreate              If containers already exist, don't recreate
                               them. Incompatible with --force-recreate and 
                               --renew-anon-volumes.
    --no-build                 Don't build an image, even if it's missing.
    --no-start                 Don't start the services after creating them.
    --build                    Build images before starting containers.
    --abort-on-container-exit  Stops all containers if any container was
                               stopped. Incompatible with --detach.
    --attach-dependencies      Attach to dependent containers.
    -t, --timeout TIMEOUT      Use this timeout in seconds for container
                               shutdown when attached or when containers are
                               already running. (default: 10)
    -V, --renew-anon-volumes   Recreate anonymous volumes instead of retrieving
                               data from the previous containers.默认recreate依然会使用原来的匿名卷。
    --remove-orphans           Remove containers for services not defined
                               in the Compose file.
    --exit-code-from SERVICE   Return the exit code of the selected service
                               container. Implies --abort-on-container-exit.
    --scale SERVICE=NUM        Scale SERVICE to NUM instances. Overrides the
                               `scale` setting in the Compose file if present.
                               开启多个实例

compose file

这里以v3版本为例。使用yaml格式配置,默认读取./docker-compose.yml(yaml也可)。

compose file文件结构

compose可以将多个服务以yaml的格式进行配置,类似Dockerfile,这也是一个配置文件

# docker-compose.yml
version: "3.9"
services:
 
  redis:
    image: redis:alpine
    ports:
      - "6379"
    networks:
      - frontend
    deploy:
      replicas: 2
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure
 
  db:
    image: postgres:9.4
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - backend
    deploy:
      placement:
        max_replicas_per_node: 1
        constraints:
          - "node.role==manager"
 
  vote:
    image: dockersamples/examplevotingapp_vote:before
    ports:
      - "5000:80"
    networks:
      - frontend
    depends_on:
      - redis
    deploy:
      replicas: 2
      update_config:
        parallelism: 2
      restart_policy:
        condition: on-failure
 
  result:
    image: dockersamples/examplevotingapp_result:before
    ports:
      - "5001:80"
    networks:
      - backend
    depends_on:
      - db
    deploy:
      replicas: 1
      update_config:
        parallelism: 2
        delay: 10s
      restart_policy:
        condition: on-failure
 
  worker:
    image: dockersamples/examplevotingapp_worker
    networks:
      - frontend
      - backend
    deploy:
      mode: replicated
      replicas: 1
      labels: [APP=VOTING]
      restart_policy:
        condition: on-failure
        delay: 10s
        max_attempts: 3
        window: 120s
      placement:
        constraints:
          - "node.role==manager"
 
  visualizer:
    image: dockersamples/visualizer:stable
    ports:
      - "8080:8080"
    stop_grace_period: 1m30s
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
    deploy:
      placement:
        constraints:
          - "node.role==manager"
 
networks:
  frontend:
  backend:
 
volumes:
  db-data:

环境变量

compose配置文件更大,需要使用变量的机会更多,可以设置ENV,然后直接使用

services:
 web:
  image:"webapp:${TAG}" # TAG首先会去当前shell下找这个环境变量,如果没有则去找指定的env文件

env文件:

  • 可以在项目目录下指定.env文件,会自动读取这里的变量
  • 可以使用--env-file指定配置文件路径

注意:以上都是给compose设置变量,用于compose构建阶段,最终是通过compose构建image,通过image启动容器。

那怎么在compose中给容器设置env呢?就像在Dockerfile中设置ENV和docker run中指定-e一样。

web:
  environment: # 直接指定环境变量
    - DEBUG=1 # 给容器设置环境变量,如果不指定=1,会去当前shell中找
  env_file: # 通过额外的文件指定环境变量
    - ./web_var.env # 直接指定./web_var.env这个路径文件,把里面的内容都当作环境变量

还可以docker-compose run -e的时候指定,类似docker run -e

优先级:

这么多地方可以指定容器的环境变量,那读取顺序呢?

  1. compose file中设置的

  2. shell 环境变量

  3. env文件

  4. Dockerfile中设置的

给服务设置启动条件

通过给服务设置profiles数组,数组中的一个生效则服务会启动。

不指定profiles的服务默认是可以被直接启动的。

version: "3.9"
services:
  frontend:
    image: frontend
    profiles: ["frontend"]
 
  phpmyadmin:
    image: phpmyadmin
    depends_on:
      - db
    profiles:
      - debug
 
  backend:
    image: backend
 
  db:
    image: mysql
  • 通过启动命令指定生效的profile

    docker-compose --profile frontend up # 此时frontend,backend,db生效
    docker-compose --profile debug up # 此时phpmyadmin,backend,db生效
  • 启动时明确启动服务,该服务的profiles自动生效

    docker-compose up phpmyadmin # 此时debug自动生效了

    注意:

    此时只会自动生效目标服务的profiles,目标所依赖的服务的profiles不会自动生效

    比如:

    version: "3.9"
    services:
     
      phpmyadmin:
        image: phpmyadmin
        depends_on:
          - db
        profiles:
          - debug
     
      db:
        image: mysql
        profiles:
          - dev

    此时:

    docker-compose up phpmyadmin # 此时会启动失败,因为默认只enable了debug,而phpmyadmin的依赖服务db的profile,dev并没有生效,导致db启动失败

    有2种解决办法:

    • 给db再添加debug的profile

        db:
          image: mysql
          profiles:
            - dev
            - debug
    • 启动时通过--profile dev让dev生效

多配置文件

一个应用根据配置文件,表现不同的模式,这是很常见的需求,比如dev,prod,test等情况。

虽然通过改变shell的环境变量,或者.env的值,能给compose file设置一定的变化,但是只通过变量无法完成更复杂的差异化配置。

此时可以使用多配置文件,并结合merge策略,完成不同模式的构建。

默认情况下,可以有2个配置文件:

  • docker-compose.yml
  • docker-compose.override.yml

docker-compose up会直接合并docker-compose.yml,docker-compose.override.yml,然后使用合并后的配置文件。

可以自定义多个配置文件,然后通过-f指定,同时-f实际可以指定多个文件,然后根据指定的顺序进行合并,特别注意路径问题,后面-f指定的文件,都是以第一个-f指定的文件作为相对路径。合并模式是后面的覆盖前面的配置,最终形成一个配置文件。

类似Dockerfile的-f,默认docker-compose up会去当前路径下找docker-compose.yml文件,也可以根据-f指定yml文件

比如:

# docker-compose.yml
web:
  image: example/my_web_app:latest
  depends_on:
    - db
    - cache
 
db:
  image: postgres:latest
 
cache:
  image: redis:latest
# docker-compose.override.yml
web:
  build: .
  volumes:
    - '.:/code'
  ports:
    - 8883:80
  environment:
    DEBUG: 'true'
 
db:
  command: '-d'
  ports:
    - 5432:5432
 
cache:
  ports:
    - 6379:6379
# docker-compose.prod.yml
web:
  ports:
    - 80:80
  environment:
    PRODUCTION: 'true'
 
cache:
  environment:
    TTL: '500'
docker-compose up 
# docker-compose.override.yml 覆盖 docker-compose.yml
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
# docker-compose.prod.yml 覆盖 docker-compose.yml

合并策略:

  • 如果是单个值得情况,比如image,存在则替换,不存在则直接使用

  • 如果是多个值得情况,基本都是以数组的形式存在的,比如expose,会取并集。

  • 如果是多个值,以key=value对的情况出现的,取并集,以key作为判断依据。

    注意:volumes的key是:后面的路径,也就是容器中的路径。


compose file V2版本(compose file的版本,不是docker compose的版本)中,引入了一个新的配置选项extends

-f的合并不同,这里可以直接继承某个文件的某个服务的所有配置内容过来

# common-services.yml
services:
  webapp:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - "/data"
# docker-compose.yml
services:
  web:
    extends:
      file: common-services.yml
      service: webapp

你可以直接使用这些继承过来的配置内容,还可以重新定义,或者添加新的配置项。

网络

通过docker compose构建的service,默认会加入一个以项目命名的bridge网络中,项目名称默认是docker-compose配置文件所在目录名,可以通过—project-name指定,或者设置COMPOSE_PROJECT_NAME环境变量。

比如项目名称为:myapp,这个默认构建的bridge网络为myapp_default。

同时构建的容器在这个网络中拥有对应的名称(hostname),即服务名,比如web,db。

...
"Networks": {
    "myapp_default": {
        "IPAMConfig": null,
        "Links": null,
        "Aliases": [
            "5113b5fb6ba5",
            "web"
        ],
        "NetworkID": "e3097d50fa66ebde46804bd5655bb2b49131c3ccc1b8e2ad75e291981f044eb7",
        "EndpointID": "ebe5b1e9ee1bbba37bbd0db445006819851c650fed424db2ff586abc29f51e74",
        "Gateway": "172.19.0.1",
        "IPAddress": "172.19.0.2",
        "IPPrefixLen": 16,
        "IPv6Gateway": "",
        "GlobalIPv6Address": "",
        "GlobalIPv6PrefixLen": 0,
        "MacAddress": "02:42:ac:13:00:02",
        "DriverOpts": null
    }
}
}
...

可以直接用这个名称来访问对应的服务,比如 web:80。

使用名称有个好处,如果compose file修改了,重新up时会删除原来的容器,并生成新的容器,同时可能指定了新的IP,但是只要service名称不变,以名称作为连接的地方就都不需要改变。

**注意:**只能在这个网络内部使用这个名称,宿主机访问,只能通过对应的容器ip以及映射的端口访问。


可以自定义网络,可以定义顶级networks,也可以定义service级networks

可以不创建默认的网络,而是加入一个已存在的网络

services:
  # ...
  service-name:
  	# 如果对于指定外部网络的需要指定IP地址的,可以如下设置
  	networks:
  	  default:
  		  ipv4_address: xxxxx	
networks:
  default: # 将下面的网络当作默认网络
    external: true # 允许加入已存在的网络
    name: my-pre-existing-network # 指定这个网络

服务启动顺序

虽然有depends_on,会在依赖是running状态时,再启动服务,但是这并不能完全保证依赖已经启动,可以借用第三方脚本

wait-for-it.sh,这个脚本会监听一个host:port,一当监听有返回时,会执行--后面的指令

version: "2"
services:
  web:
    build: .
    ports:
      - "80:8000"
    depends_on:
      - "db"
    command: ["./wait-for-it.sh", "db:5432", "--", "python", "app.py"]
  db:
    image: postgres

等待db:5432这个端口能进行响应了,会执行python app.py这个指令,也就是启动web服务了。

compose file 配置

build

调用dockerfile进行build image

2种用法:

  • 直接一个目录作为context,会自动取寻找这个context下的Dockerfile

    version: "3.9"
    services:
      webapp:
        build: ./dir
  • 以object的形式配置更详细的内容

    verson: "3.9"
    services:
      webapp: 
        build: 
          context: ./dir # 指定上下文路径
          dockerfile: dockerfiles/Dockerfile.base # 指定Dockerfile路径
          args: # 配置Dockerfile中的ARG参数
            - arg1=1 # 不指定值会去找compose下的环境变量
            - arg2=2
        image: webapp:tag # 构建的镜像名称,替换默认的image名称
cache_from

构建镜像时使用相关镜像的缓存

build:
  context: .
  cache_from:
    - alpine:latest
    - corp/web_app:3.14
network

配置网络,默认compose会创造一个project-name_default的bridge网络,并将所有服务加入其中。

这里可以配置使用host,none,自定义网络

build:
  context: .
  network: host
target
cap_add,cap_drop

容器的权限修改,比如设备的访问权限,这在docker cli的run命令中提到过

cap_add:
  - ALL
 
cap_drop:
  - NET_ADMIN
  - SYS_ADMIN
command

容器启动后执行的命令,类似Dockerfile中的CMD,也有2种写法,建议使用execform格式

command: ["top","-d","5"]
container_name

默认会以project-name_service-name_sequence这样的格式生成容器名称

depends_on

通过依赖关系,构建启动,停止顺序

version: "3.9"
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

启动时,先启动redis和db,停止时同样

而且单独启动某个服务,也会顺带启动depends_on下的服务。

docker-compose up SERVICE # 会自动启动SERVICE下的depends_on
dns

自定义DNS

dns: 8.8.8.8
dns: 
  - 8.8.8.8
  - 9.9.9.9
entrypoint

覆写默认的entrypoint

entrypoint: ["top","-b","-n","10"]
env_file

给service设置环境变量。默认是以项目目录为相对路径,如果通过-f指定了compose file,则是以这个compose file作为相对路径。

env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/runtime_opts.env # 下面的会覆盖上面的同名变量
environment
environment:
  - RACK_ENV=development
  - SHOW=true # 如果是对象形式,true,false等需要被'true'包裹,以免被解析
  - SESSION_SECRET # 不指定值,则取当前compose下的环境变量
expose

暴露端口,并没有映射,只能被相关的服务访问,也就是同一网段的服务访问

expose:
  - "3000"
  - "8000"
extra_hosts

添加额外的域名映射,类似docker run --add-hosts

extra_hosts:
  - "somehost:162.242.195.82"
  - "otherhost:50.31.209.229"

对应的服务启动的容器/etc/hosts中会添加对应的记录

healthcheck

健康检查

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"]
  interval: 1m30s
  timeout: 10s
  retries: 3
  start_period: 40s

test如果是以execform格式时,第一个元素必须是NONE,CMD,CMD-SHELL

  • NONE表示不进行healthcheck,就算image/Dockerfile设置了healthcheck,这里也会取消
  • CMD表示直接允许,而不是在一个shell中允许
  • CMD-SHELL,在shell,默认/bin/sh中允许,test如果是shellform格式,默认就是在/bin/sh中允许
image

指定镜像

image: ubuntu:18.04
logging

日志选项

version: "3.9"
services:
  some-service:
    image: some-service
    logging:
      driver: "json-file" # 日志驱动,类似run --log-driver。只有json-file,syslog,none三个选项
      options: # 选项,类似run --log-opt
        max-size: "200k" # 单个日志文件的大小
        max-file: "10" # 日志文件的数量,如果超过了就删除最旧的那个
network_mode

docker run --network一样。针对单个服务,比如有2个服务,一个使用了host,一个默认的,那么依然会创建一个以这个项目命名的bridge网络,然后默认的那个服务加入这个桥接网络,host那个使用主机的network namespace;但是如果2个都是host,是不会创建桥接网络的。

version: "3.9"
services: 
  web:
    image: nginx
    network_mode: host # 会和主机使用一个network namespace,能看到主机的所有网络设备
  db:
    image: mysql # 此时nginx处于主机网络中,依然可以通过这个yml生成的虚拟网桥连接网桥中的其他服务

可以设置bridge,none,host,其他服务的network,其他容器的network

network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
network

配置容器连接的网络,这些网络需要提前在顶级选项下配置

services:
  some-service:
    networks:
     - some-network
     - other-network
networks:
  some-network:
  other-network:
修改默认网络配置
services:
  # ...
networks:
  default:
    external: true
    name: my-pre-existing-network # 此时默认网络会去外部找这个网络,比如docker network create my-pre-exist...
services:
  # ...
networks:
  default:
    driver: custom-driver-1 # 给默认网络指定自定义driver
指定外部网络
version: "3.9"
networks:
  outside:
    external: true # 不会创建一个project-name_outside网络,而是去外部找outside这个已经存在的网络
version: "3.9"
networks:
  outside:
    external: true
    name: actual-name-of-network # 去外部找actual-name-of-network这个网络,并且引用名称为outside
alias

在不同的网络给当前服务指定别名,同一个网络下可以用这个alias连接到这个服务

services:
  some-service:
    networks:
     - some-network
       aliases: 
         - alias1
         - alias2
     - other-network
       aliases: 
         - alias1
         - alias4
network:
  - some-network
  - other-network

alias是给自己取别名,通过这个名字可以连接到这个服务,—add-hosts是添加host,比如 172.19.0.2 service1

ipv4

可以给服务配置静态ip地址,前提是顶级networks下需要配置ipam,并指定subnet

version: "3.9"
 
services:
  app:
    image: nginx:alpine
    networks:
      app_net:
        ipv4_address: 172.16.238.10
        ipv6_address: 2001:3984:3989::10
 
networks:
  app_net:
    ipam:
      driver: default
      config:
        - subnet: "172.16.238.0/24"
        - subnet: "2001:3984:3989::/64"
pid
pid: "host" # 和主机共享pid维度的命名空间
ports

暴露并映射端口

ports:
  - "3000"  # host指定随机端口映射
  - "3000-3005" # host指定随机端口段映射
  - "8000:8000" # host8000:container8000
  - "9090-9091:8080-8081" # host端口段:container端口段
  - "49100:22" # 映射不同的端口
  - "127.0.0.1:8001:8001" # 指定网络接口映射,默认是0.0.0.0,也就是所有网络接口
  - "127.0.0.1:5000-5010:5000-5010" # 指定网络接口映射
  - "127.0.0.1::5000" # 指定网络接口,随机端口映射
  - "6060:6060/udp" # upd协议映射
  - "12400-12500:1240" # 一个端口映射到host的端口段,注意不能反过来

还有一种object的映射方式

ports:
  - target: 80 # 容器端口
    published: 8080 # 主机端口
    protocol: tcp # 协议
    mode: host # 模式,只有host和ingress2种模式,ingress用于docker swarm
profiles

控制service是否启动,不声明这个属性,表示默认启动

profiles:
  - debug
  - test
restart

重启策略,和docker run,dockerfile中相同设置

restart: "no" # 默认值,不重启
restart: always # 一直重启
restart: on-failure # exit code不为0时,会重启
restart: unless-stopped # 一直重启,除非已经stopped
secrets

存储机密信息

version: "3.9"
services:
  redis:
    image: redis:latest
    deploy:
      replicas: 1
    secrets:
      - my_secret  # 引用secret,同时将对应的文件挂载进容器的/run/secrets/my_secret
      - my_other_secret
secrets: # 被使用的机密文件,必须先在顶级结构中定义
  my_secret: # 机密的名称
    file: ./my_secret.txt # 具体内容
  my_other_secret: 
    external: true # 表示是外部定义的secret,比如docker secret create my_other_secret - (从stdin生成secret)

还有一种object写法,可以进行更细粒度的控制

version: "3.9"
services:
  redis:
    image: redis:latest
    deploy:
      replicas: 1
    secrets:
      - source: my_secret # 表示引用的是哪个secret
        target: redis_secret # 表示挂载进容器/run/secrets/redis_secret这个目录,默认是source的名称
        uid: '103' # uid,默认0
        gid: '103' # gid, 默认0
        mode: 0440 # 权限,默认0444,具有读权限
secrets:
  my_secret:
    file: ./my_secret.txt
  my_other_secret:
    external: true
stop_grace_period

在发送stop_signal指定的停止信号并没有被响应时,开始尝试stop的时间,时间超过还没有停止则发送SIGKILL信号杀死进程,默认10s

stop_signal

stop信号,默认SIGNTERM(15),可以自行指定。

tmpfs

挂载临时文件系统

- type: tmpfs # 挂载临时文件系统
  target: /app # 容器内的目录
  tmpfs: # 配置相关选项
    size: 1000
volumes

容器卷挂载

可以使用short语法或long语法格式,都是围绕容器卷,匿名卷,host path,tmpfs几种情况展开

具名卷必须先在顶级结构volumes下声明,才能在service上使用。(具名卷一般用于多个服务共享一个容器卷)

version: "3.9"
services:
  web:
    image: nginx:alpine
    volumes:
      - type: volume # long语法格式,指定类型,volume,type,tmpfs
        source: mydata # 如果是volume,此时source可以是具名卷名称,如果省略就是匿名卷
        target: /data # 容器内目录,也就是挂载点
        volume: # 配置volume属性
          nocopy: true # 不进行自动复制,挂载点目录里的内容不自动复制进容器卷中
        read_only: true # 设置容器卷权限为只读,默认rw
      - type: bind # 类型为bind
        source: ./static # source可以是相对路径或绝对路径,相对路径以compose file路径作为参照
        target: /opt/app/static # 容器内目录
      - type: tmpfs # tmpfs类型
      	target: /run # 此时source不再需要
      	tmpfs: # 配置tmpfs属性
      	  size: 200 # 默认单位bytes,可以指定string格式,200b,200kb,200mb,200gb
 
  db:
    image: postgres:latest
    volumes: # short语法格式
      - "/var/run/postgres/postgres.sock:/var/run/postgres/postgres.sock" # 实际就是bind host上的path
      - "dbdata:/var/lib/postgresql/data" # 挂载具名卷
      - "~/configs:/etc/configs/:ro" # 最后可以设置mode,默认rw
      - "/var/lib/postgres" # 挂载匿名卷
 
volumes: # 声明具名容器卷,可多服务共享
  mydata:
  dbdata:
  otherdata:
    external: true # 引用已经存在的具名卷,比如docker volume create otherdata
    name: volume-name # 还可以以name的形式指定外部已经创建好的容器卷名称,这样引用的时候可以指定不同的名称
domainname, hostname, ipc, mac_address, privileged, read_only, shm_size, stdin_open, tty, user, working_dir

这些单属性和docker run中的表现一致

比如hostname

service:
  web:
    hostname: my-web #此时这个服务实际上有3个Alias,containerId,web,my-web,通过这些名称都可以直接访问(同一network)