美文网首页
Docker基础知识总结

Docker基础知识总结

作者: 天剑飞心 | 来源:发表于2019-02-16 00:38 被阅读0次
    Docker的优势

    可以将Docker的产生理解为一种相比虚拟机更加轻量级的虚拟化技术,前有Openstack用来在物理机上面构建出物理资源,以及很早以前就有的Linux NameSpace技术,Docker也就应运而生,称为承载单独进程/服务的载体;
    Docker的优势在于使用简单(最初设计的时候就没有考虑过多的功能),可以运行在各种环境上面(物理机、虚拟机、个人PC、云主机),可移植性也很好(统一使用Docker-hub,或者重载别人的Dockerfile)


    如何运行一个Docker容器

    首先获取一个Docker镜像,一般从镜像仓库当中拉取(有公网上的各个镜像仓库,类似于apt、yum源),拉取的过程当中可以看到本地主机会下载一个镜像压缩包并解压,拉取镜像成功之后可以使用docker images查看本地的所有镜像信息,信息主要包括镜像名,镜像的tag(一般用于表示同一镜像的不同版本,比如ubuntu的版本就通过tag号来体现),镜像的ID以及镜像的大小
    然后就可以使用Docker RUN命令来给予选定的镜像创建容器了,命令格式为
    docker run [参数] [镜像源] [容器启动的命令参数]
    其中run后面可以附带多个参数,比如指定容器的映射端口、容器的网络模式、容器的挂载Volume、容器后台运行等等,镜像源就是之前拉取的镜像名称:镜像Tag,这里需要注意的是如果命令当中定义的镜像源在本地找不到的话,Docker会主动去Docker-hub上面寻找镜像,最后是容器启动的时候的命令参数,这里可以运行一条或者多条命令,比如最简单的场景,我们写一个/bin/bash,代表运行容器的时候一并运行一个bash shell终端,这里其实也可以运行多条命令,比如我们要在指定的目录下面运行某一个shell脚本,可以附带这个命令 /bin/bash -c "命令1&&命令2&&命令3"
    运行完成容器之后,我们可以使用docker ps命令来查看当前正在运行的容器,可以看到docker同样会为每一个容器都分配一个ID作为标识(其实这里只是短ID),同时会显示运行时间和端口号等信息;
    以这里ubuntu容器为例,我们可以使用docker attach命令进入到容器当中,进去之后可以看容器内部的目录结构和Ubuntu系统的目录结构几乎一模一样,但是查看一下当前系统的进程,发现当前容器内部仅有一个/bin/bash进程在运行(而这个进程正是我们在docker run命令里面指定的),再进一步进入/proc目录下面查看当前系统的配置,信息都是和docker宿主机的信息一模一样,我推测这些文件压根就是宿主机的文件(这部分和宿主机共用了相同的Namespace),由此可见Docker容器是为了容器内运行的进程服务的,只为进程提供可以正常运行的各种必要条件,而不像通常主机的系统需要为成百上千个进程服务


    如何构建自定义的Docker镜像

    一般有两种方法来构建自定义的Docker镜像

    • 运行一个基础的通用系统镜像(docker-hub上面的ubuntu、centos等系统镜像),然后创建并进入容器,在容器当中按照平时在系统上面的配置来完成自己想要的配置、服务部署,最后使用docker commit命令将容器保存在镜像
    • 使用Dockerfile来构建镜像(推荐方式),Dockerfile相当于一个Docker能够读懂的描述文件,里面会指定Docker的基础镜像,以及在基础镜像之上做的各种操作(文件拷贝,容器内部命令执行,容器启动时附带的命令),重要的是Dockerfile可读性比较好,你的Dockerfile可以清晰的让别人知道你在构建镜像的过程当中做了哪些动作,另外一个方面,使用Dockerfile方式构建的镜像实际是一个多层的镜像(在最初的基础镜像之上每执行一条Dockerfile的命令都相当于是给这个镜像套了一层壳),别人甚至可以通过修改我们的镜像文件来方便进行修改(比如把某一层的操作置空)

    Dockerfile创建镜像的具体步骤

    首先需要创建一个dockerfile的目录,目录内部创建一个名为Dockerfile的文件,同时可以放置一些后续将会拷贝到容器当中的文件,创建好Dockerfile之后使用docker build命令来生成我们的自定义镜像。具体的一些Dockerfile重要命令如下:

    • FROM:定义自定义镜像使用的基础镜像,这里通常为镜像仓库里面的通用系统镜像
    • ENV:在Dockerfile内声明一个变量,供后面的命令使用
    • ADD:从宿主机上面拷贝文件到容器内的指定目录下
    • COPY:作用与ADD类似,也是拷贝文件到容器,但是ADD的功能更加强大一些,比如ADD可以在拷贝的过程当中对压缩文件进行解压,同时ADD支持从URL获取文件(但是这里有坑,就是如果目标URL需要认证的话,ADD命令是无法附带认证信息的)
    • RUN:在容器当中运行一条命令,任何命令都可以,但是命令出错的话会导致docker build过程中断(比如RUN一条apt-get intall命令中间有输入Y的操作,这个时候就需要使用管道命令了,可以自动输入Y、Yes等选择)
    • VOLUME:为容器添加一个挂载卷(在容器创建的时候Docker会默认在宿主机/lib/docker/container下面创建一个目录作为Docker的挂载卷),用户也可以手动定义挂载卷,比如多个容器挂载同一个Volume实现数据共享,比如自定义一个目录作为Volume来保存容器运行时候生成的日志,Volume在容器停止、删除之后并不会被清除,会一直存在(当然这应该还是有些坑的,比如Docker运行长时间之后会产生很多的垃圾Volume,占用宿主机的磁盘空间)
    • EXPOSE: 这个命令其实只是在dockerfile当中指示容器将会暴露的端口号,具体暴露指定端口号的操作需要在docker run的时候使用-p指定或者-P由宿主机分配动态端口
    • CMD以及ENTRYPOINT:这两个命令放在一起写一下,之前我一直都只用过CMD没用过ENTRYPOINT,然而看了看Docker文档中的定义,这两个命令都是有明确的使用场景定义的

    The main purpose of a CMD is to provide defaults for an executing container. These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINT instruction as well.

    The CMD instruction has three forms:
    CMD ["executable", "param1", "param2"](exec form, this is the preferred form)
    CMD ["param1","param2"](as default parameters to ENTRYPOINT)
    CMD command param1 param2(shell form)

    可以看出,CMD是作为一个默认的执行参数来配合容器运行的,CMD可以定义一条独立的参数(带中括号的标准形式以及不带中括号的默认shell执行模式),也可以只定义参数辅助ENTRYPOINT执行;而ENTRYPOINT的语法与CMD类似,只是ENTRYPOINT可以只定义executable命令,参数可以由CMD补充或者再docker run的时候添加,同时ENTRYPOINT也不会简单被docker run后面跟的命令覆盖掉(可以手动添加--entrypoint参数来覆盖dockerfile当中的entrypoint)


    Docker的网络原理

    这里只简单写一点原生的Docker网络原理,不去考虑开源网络组件或是k8s的网络实现,首先来看一看Docker依托的几个Linux网络原理:

    • 网络命名空间: 网络命名空间也是命名空间的一种,具体实现原理相当于是在创建网络设备的时候添加私有全局变量(变量以命名空间的名称命名),实现网络设备绑定,也实现了不同网络命名空间之间的隔离(因为收发流量的网络设备都不一样了)
    • veth: veth为一对虚拟网卡,前面说了Docker使用不同的网络命名空间来实现网络上的隔离,那么veth就可以作为不同网络空间链接的通道,一对veth的特性是在其中一个veth上进行数据收发,另外一个veth上面也能有相同的数据收发
    • netfilter和iptables: netfilter定义了一些钩子函数,可以让Linux在处理数据包的各个阶段做一些用户自定义的动作,钩子函数的挂载点包括INPUT、PREROUTE、FORWARD、AFTERROUTE、OUTPUT五个时间点;iptables定义了一系列的ip包处理规则表,规则表有RAW、MANGLE、NAT、FILTER四种类型,优先级依次降低,具体的规则表定义了挂载点、表类型、匹配参数、匹配动作这些信息
    • 网桥: 网桥可以实现2层网络的交换,通过学习不同端口的MAC地址,在接收到数据包的时候根据目的地址进行转发

    在实际Docker应用中,Docker需要解决的场景包括:

    • 容器与容器的网络
    • 容器到外部的网络
    • 外部到容器的网络

    而Docker容器的网络模式有4种:

    • bridge模式:表示容器使用网桥模式,这也是docker默认的容器网络模式,网桥模式会在创建容器的时候在容器内部和docker0网桥上面创建一对veth,容器之间通过docker0网桥中转进行收发数据包
    • host模式:为容器分配实际的ip地址,容器与宿主机之间,设置与外部网络之间直接进行网络交互(前提是添加好收发的路由)
    • container模式: 创建容器的时候指定使用另外一个容器的网络(比如k8s当中pod内的容器都是共享了pause容器的网络)
    • none模式: 支持用户高度自定义的网络模式

    以默认的网桥模式为例,简单分析一下网络实现的细节,在安装好docker-engine之后,我们可以看到系统除loopback,eth0之外,还出现了一个docker0设备(拥有一个172.17开头的16位地址段,docker0本身使用了第一个地址)

    root@xiaohu-MS-7B23:/home/xiaohu# ip addr
    1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
        link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
        inet 127.0.0.1/8 scope host lo
           valid_lft forever preferred_lft forever
        inet6 ::1/128 scope host 
           valid_lft forever preferred_lft forever
    2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
        link/ether 00:d8:61:0c:aa:5d brd ff:ff:ff:ff:ff:ff
        inet 192.168.31.242/24 brd 192.168.31.255 scope global dynamic noprefixroute eno1
           valid_lft 42712sec preferred_lft 42712sec
        inet6 fe80::b507:cfa1:faad:98d6/64 scope link noprefixroute 
           valid_lft forever preferred_lft forever
    3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
        link/ether 02:42:c1:ad:63:12 brd ff:ff:ff:ff:ff:ff
        inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
           valid_lft forever preferred_lft forever
    

    再看一下当前的iptable,在NAT表当中,第一条表示在进行路由表匹配之前,将所有目的地址为本机地址的数据包都送到DOCKER规则的表当中进行匹配,第三条表示在路由匹配完成之后,将源地址为容器,目的地址不为docker0的数据包都经过地址转换(MASQUERADE,将容器地址转换为宿主机的地址)

    root@xiaohu-MS-7B23:/home/xiaohu# iptables-save
    # Generated by iptables-save v1.6.1 on Mon Feb 25 23:37:34 2019
    *nat
    :PREROUTING ACCEPT [5:463]
    :INPUT ACCEPT [5:463]
    :OUTPUT ACCEPT [268:17428]
    :POSTROUTING ACCEPT [268:17428]
    :DOCKER - [0:0]
    -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
    -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
    -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
    -A DOCKER -i docker0 -j RETURN
    COMMIT
    # Completed on Mon Feb 25 23:37:34 2019
    # Generated by iptables-save v1.6.1 on Mon Feb 25 23:37:34 2019
    *filter
    :INPUT ACCEPT [2769:3426683]
    :FORWARD DROP [0:0]
    :OUTPUT ACCEPT [2760:295032]
    :DOCKER - [0:0]
    :DOCKER-ISOLATION-STAGE-1 - [0:0]
    :DOCKER-ISOLATION-STAGE-2 - [0:0]
    :DOCKER-USER - [0:0]
    -A FORWARD -j DOCKER-USER
    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
    -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    -A FORWARD -o docker0 -j DOCKER
    -A FORWARD -i docker0 ! -o docker0 -j ACCEPT
    -A FORWARD -i docker0 -o docker0 -j ACCEPT
    -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
    -A DOCKER-ISOLATION-STAGE-1 -j RETURN
    -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
    -A DOCKER-ISOLATION-STAGE-2 -j RETURN
    -A DOCKER-USER -j RETURN
    COMMIT
    # Completed on Mon Feb 25 23:37:34 2019
    

    此时我们创建一个容器,容器使用了动态端口映射,然后再来看一看iptables,在NAT的表当中,增加了两条规则,第四条有点看不懂,猜测可能是docker新版本做的优化(目的和源地址一模一样还需要作NAT
    转换吗,会不会是防止宿主机之间相同地址段冲突呢),第六条表示经过第一条和第二条规则匹配到的目的地址为宿主机本地地址的包,如果目的端口为动态分配的端口(此处为32768),则转换为容器的地址以及暴露端口(22端口),这样外部的主机就可以通过动态映射端口来访问我们的容器了。

    root@xiaohu-MS-7B23:/home/xiaohu# docker ps
    CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                   NAMES
    9efb0e0685ba        ubuntu:latest       "/bin/bash"         4 seconds ago       Up 2 seconds        0.0.0.0:32768->22/tcp   condescending_shtern
    root@xiaohu-MS-7B23:/home/xiaohu# 
    root@xiaohu-MS-7B23:/home/xiaohu# 
    root@xiaohu-MS-7B23:/home/xiaohu# iptables-save
    # Generated by iptables-save v1.6.1 on Tue Feb 26 00:39:21 2019
    *nat
    :PREROUTING ACCEPT [0:0]
    :INPUT ACCEPT [0:0]
    :OUTPUT ACCEPT [17:1311]
    :POSTROUTING ACCEPT [17:1311]
    :DOCKER - [0:0]
    -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
    -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
    -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 22 -j MASQUERADE
    -A DOCKER -i docker0 -j RETURN
    -A DOCKER ! -i docker0 -p tcp -m tcp --dport 32768 -j DNAT --to-destination 172.17.0.2:22
    COMMIT
    # Completed on Tue Feb 26 00:39:21 2019
    # Generated by iptables-save v1.6.1 on Tue Feb 26 00:39:21 2019
    *filter
    :INPUT ACCEPT [50:5673]
    :FORWARD DROP [0:0]
    :OUTPUT ACCEPT [54:5312]
    :DOCKER - [0:0]
    :DOCKER-ISOLATION-STAGE-1 - [0:0]
    :DOCKER-ISOLATION-STAGE-2 - [0:0]
    :DOCKER-USER - [0:0]
    -A FORWARD -j DOCKER-USER
    -A FORWARD -j DOCKER-ISOLATION-STAGE-1
    -A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
    -A FORWARD -o docker0 -j DOCKER
    -A FORWARD -i docker0 ! -o docker0 -j ACCEPT
    -A FORWARD -i docker0 -o docker0 -j ACCEPT
    -A DOCKER -d 172.17.0.2/32 ! -i docker0 -o docker0 -p tcp -m tcp --dport 22 -j ACCEPT
    -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
    -A DOCKER-ISOLATION-STAGE-1 -j RETURN
    -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
    -A DOCKER-ISOLATION-STAGE-2 -j RETURN
    -A DOCKER-USER -j RETURN
    COMMIT
    # Completed on Tue Feb 26 00:39:21 2019
    

    那么容器访问外部是如何实现的呢,在默认的bridge模式下,容器的包走向外部都是需要通过docker0网桥中转的,一旦包到了docker0之后,由于docker0是属于系统的网络namespace的,所以能够去匹配系统的路由表,在路由表当中明确指示了要把数据包发往外部需要的默认路由,匹配完路由表之后,再去执行iptables当中的OUTPUT与POSTROUTE动作,在这里就是-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE这一条规则了,说明了发往外部的包需要进行NAT地址转换,将源地址由容器地址转换为宿主机的网卡地址,这样就完成了容器到外部的发包

    root@xiaohu-MS-7B23:/home/xiaohu# netstat -rn
    Kernel IP routing table
    Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
    0.0.0.0         192.168.31.1    0.0.0.0         UG        0 0          0 eno1
    169.254.0.0     0.0.0.0         255.255.0.0     U         0 0          0 eno1
    172.17.0.0      0.0.0.0         255.255.0.0     U         0 0          0 docker0
    192.168.31.0    0.0.0.0         255.255.255.0   U         0 0          0 eno1
    

    以上就是当前我所了解的一些Docker网络知识了,从上面这几个原理实现我们可以看出,Docker基本对于异主机之间的容器通信是没有支持的,每一台主机上面都默认规划了同一地址段来供容器使用,后面学习k8s的过程当中再去对异主机的容器通信进行深入学习,比如使用flannel组建在创建容器之时就针对每个宿主机划分不同的地址段避免冲突并添加路由,使用OVS构建宿主机之间的容器隧道等方案

    相关文章

      网友评论

          本文标题:Docker基础知识总结

          本文链接:https://www.haomeiwen.com/subject/gswveqtx.html