2.Docker容器学习之新生入门必备基础知识
阅读原文时间:2023年07月10日阅读:3

目录

0x02 Docker 核心概念

描述:Docker的三大核心概念镜像/容器和仓库, 通过三大对象核心概念所构建的高效工作流程;

1.镜像 [image]

描述:images 类似于虚拟机镜像,借鉴了Git利用分成分层优点,通过文件系统分层的概念实现了分层复用,极大的节约了磁盘空间;简单的您可以将它理解成为一个面向Docker引擎的只读模板包含文件系统;

  • 镜像是创建Docker容器的基础,然后通过版本管理和增量的文件系统;
  • 用户基于镜像来运行自己的容器,镜像是基于 Union 文件系统的层式结构;

Docker运行容器前本地必须存在对应的镜像,如果不指定镜像名将会默认自动采用Docker Hub公共注册服务器仓库中该名称的镜像;
Docker 镜像使用帮助:https://lug.ustc.edu.cn/wiki/mirrors/help/docker

基础知识:

  • 1.镜像名称格式:Image hub address/Namespace/Repository:taghub.weiyigeek.top/Test//alpine-apps:1.0
  • 2.典型Linux启动需要运行两个FS即:Kernel > Bootfs > Rootfs
  • 3.镜像的分层结构: 新镜像是从Base镜像一层一层叠加的,每安装一个软件就在现有镜像基础上增加一层;

镜像操作关键点:

docker search [镜像名称] #搜索关于Archlinux镜像
#支持的参数:
--automated=false #仅仅显示自动创建的镜像(官方)
--no-trunc=false  #输出信息不截断显示
-s,--stars=0    #指定仅显示评价为指定星级以上的镜像

#仓库名(Repository) 或者 标签名[不指定着默认latest,即最新]
docker pull name/[repository[:tag]] #获取Hub镜像如果不指定TAG将默认选择仓库中最新颁布的镜像
docker push DockerHubUser用户/test:latest  #上传到docker仓库

docker images   #列出本机已有镜像

docker tag 原仓库[:标签] 新仓库名[:标签]  #为本地镜像添加一个新标签 [注意仓库名称必须小写]
docker tag <image id> username/name:devel #修改镜像的标签

docker inspect [image id]  #获取镜像的详细信息
docker inspect -f {{".Architecture"}} 550(images Id 前面3位)  #-f 获取单个属性 返回JSON

docker rmi [<image id>|<repository> ]   #删除镜像 镜像id|仓库名称
docker rmi -f <images id>   #不建议强行删除正在容器中运行的镜像文件

docker save -o 保存文件名.tar  [repository[:tag]]  #将镜像文件打包存出到磁盘
docker save [repository[:tag]] > 保存文件名 #将镜像文件打包存出到磁盘

docker load --input 保存文件名.tar   #将打包的镜像文件进行载人
docker load < 保存文件名.tar

运行案例:

$ sudo docker pull ubuntu:16.04                #下载14.04 tag的镜像
Using default tag: latest
latest: Pulling from library/ubuntu
#从下面可以看出镜像文件一般是由若干组成,行首f476d66f5408代表了各层的ID,下载过程中会获取并输出镜像各层信息
#层(Layer)其实是AUFS(Advanced Union File System联合文件系统)重要概率是实现增量保存于更新的基础
f476d66f5408: Pulling fs layer
8882c27f669e: Pulling fs layer
d9af21273955: Pulling fs layer
f5029279ec12: Waiting

$ sudo docker pull registry.hub.docker.com:5000/ubuntu   #从指定长仓库进行下载

$ sudo docker run -t -i Ubuntu /bin/bash        #利用dockerubuntu镜像创建一个容器 仓库后面是接的命令
root@634e7ed26d76:/# w
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
 03:34:47 up  1:59,  0 users,  load average: 0.00, 0.05, 0.06
root@634e7ed26d76:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.2 LTS"

$ sudo docker images   #列出镜像
#其中Image id的信息十分重要,它唯一标示了镜像,Tag信息来区分发行版本(如14.04,16.04)
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              d131e0fa2585        9 days ago          102MB
centos              latest              9f38484d220f        7 weeks ago         202MB
#REPOSITORY (来自哪个仓库)  TAG (镜像的标签信息)   IMAGE ID (镜像的ID号,唯一)    CREATED [Created:建立时间] VIRTUAL SIZE (镜像大小)

$ sudo docker tag ubuntu:latest ubuntu18.04:latest #修改镜像名称使用该方式进行添加新标签原来的不会被删除
$ sudo docker tag d131e0fa2585 ubuntutls:latest
$ sudo docker images
#更改后镜像的ID是完全一致的,其实都指向了同一个镜像文件相当于别名而已;
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntutls           latest              d131e0fa2585        9 days ago          102MB
ubuntu18.04         latest              d131e0fa2585        9 days ago          102MB 

$ sudo docker inspect d131e0fa2585 #获取该镜像的详细信息
$ sudo docker inspect -f {{".Architecture"}} d131  #获取系统架构 通常使用镜像ID的前三位
amd64

当该镜像在容器运行存在的时候,镜像文件默认是无法被删除的;必须停止/删除容器ID才能删除镜像文件;

#当同一个镜像有多个标签的时候rmi命令只是删错了该进行的标签而且,并不影响镜像文件
#但当只剩下一个标签的时候就要小心了,如果在停止的状态下再次使用rmi命令则会删除该镜像
$ sudo docker rmi ubuntu18.04
Untagged: ubuntu18.04:latest
$ sudo docker rmi ubuntu ubuntutls
Untagged: ubuntu:latest
Untagged:  #删除了这个镜像文件的所有AUFS层
ubuntu@sha256:d26d529daa4d8567167181d9d569f2a85da3c5ecaf539cace2c6223355d69981

$ sudo docker rmi -f ubuntutls  #强制删除
Untagged: ubuntutls:latest
Deleted: sha256:d131e0fa2585a7efbfb187f70d648aa50e251d9d3b7031edf4730ca6154e221e

创建镜像有三种办法:

  • 1.从已有镜像的容器创建

  • 2.基于本地模板导入:使用操作系统模板导入一个镜像文件;这里推荐使用OpenVZ提供的模板来常见

  • 3.基于Dockerfile导入

    ######### 从已有镜像创建 ############
    $sudo docker commit [option-选项] ContainerId [Repository[:Tag]]
    $sudo docker commit -m "xx" -a "oo" ContainerID(被修改过的ID) [Repository[:Tag]]

    -a,--author="作者"

    -m,--message="更改信息"

    -p,--pause=ture 提交时暂停容器Container运行

    $sudo docker run -it centos:latest /bin/bash
    [root@32a481e170c6 ~]$ touch {1..10}.txt #上面这个容器ID非常重要在进行修改之后

    $sudo docker container ls -a #查看容器记录
    #创建一个新的镜像
    $sudo docker commit -m "Zabbix base in Centos7" -a "Weiyigeek" 32a481e170c6 centoszabbix:latest
    sha256:680ddb57c4b80c625ef68e113f553ee932a06f25d4685d25a0b6464cf5d60982 #成功会给出一个镜像ID

    ######### 从本地模板导入 ############
    $sudo cat ubuntu-14.04.tar.gz | docker import - ubuntu:14.04 #本地导入镜像命令

存出和载人镜像并上传镜像到DockerHub中实例:

$docker save -o zabbixcentos.tar centoszabbix:latest
ls -alh zabbixcentos.tar
-rw------- 1 root root 200M 5月   6 13:16 zabbixcentos.tar
$docker load --input zabbixcentos.tar
Loaded image: centoszabbix:latest

$docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centoszabbix (上面从已有镜像文件中创建)       latest              622b7661c06b        13 minutes ago      202MB

#用户需要在DockerHub网站注册后才能进行上传镜像
$docker login  #在及其上进行登录
# Login with your Docker ID to push and pull images from Docker Hub. If you don have a Docker ID, head over to https://hub.docker.com to create one.
# Username: weiyigeek
# Password: 输入hub密码
# WARNING! 账号密码base64编码存放于 /root/.docker/config.json 文件中
# echo "d2VpeW********VrO***ldlaXllMjAxOQ==" | base64 -d
# weiyigeek:Docker
# https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded  #表示登录成功

$docker push weiyigeek/czabbix:latest
The push refers to repository [docker.io/weiyigeek/czabbix]
484404f26982: Pushed
d69483a6face: Mounted from library/centos
latest: digest: sha256:c1d891c2d56a0677ff28639189fa7252790d3b8ef417816570fb3a7654bc4e05 size: 736  #表示上传成功返回的票据

镜像的F&Q相关问题

Q:Docker 镜像是怎么实现增量的修改和维护的?
答:每个镜像都由很多层次构成,Docker 使用 Union FS 将这些不同的层结合到一个镜像中去。
通常 Union FS 有两个用途:(Docker 在 AUFS 上构建的容器也是利用了类似的原理)

  • 一方面可以实现不借助 LVM、RAID 将多个 disk 挂到同一个目录下
  • 就是将一个只读的分支和一个可写的分支联合在一起,Live CD 正是基于此方法可以允许在镜像不变的基础上允许用户在其上进行一些写操作

Q:什么是镜像写时复制?
答: 比如典型的Linux启动首先会将rootfs置为readonly再进行一系列检查后将其切换为readwrite供用户使用,当在Docker中起初也是将rootfs置为readonly,然后利用union mount将一个readwrite文件系统挂载只读的rootfs之上,并且运行再次将下层的文件系统设置为readonly并且向上叠加,我们把这样一组readonly和一个writeable的结构构成一个容器的运行目录,每一个被称为一个Layer;

Writable : Container  (可以向上叠加同时它变成一层Image)
add Apache : Image (Reference parent-引用父)
add Emacs : Image  (Reference parent-引用父)
Debian : Base Image
> Bootfs
lxc,aufs,brtfs : Kernel

Q:Docker 中 save 和 export 命令的区别?
答: 我们先简单的说一下其区别,在随后的的例子中使您更快的理解;

  • 1.save 与 load 命令对应即导出与导入镜像,而export与import命令对应即导出导入容器;

  • 2.save 保存后 load 加载的镜像没有丢失历史和层(Layer),而容器export导出然后import导入时所有的提交历史将会丢失,这意味着您无法回滚到之前的层;

  • 3.补充:通过import导入的方式镜像只有一层,而通过commit的方式生成的镜像实际是在原有的Base Image(即复写层)上又生成了一层;

    1.在一个正在运行的容器上创建一个目录然后commit

    docker exec -it test1 mkdir /tmp/demo
    docker commit -a "WeiyiGeek" -m "create /tmp/demo directory" test1 test:1.2 #加上tag镜像名称:版本防止玄虚镜像
    sha256:30a048249000dd36561bffaa9fecc7690a45ef12096849c77afd4543b0d2d9b0

    2.采用save命令进行导出提交的镜像

    docker save -o save.tar 30a048

    3.采用export进行导出正则运行的容器

    docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    25d2d645bfc9 test1 "top -b -d 2" 2 weeks ago Up 3 days test1
    docker export test1 > export.tar # 或者 docker export 25d > export.tar

    4.在另外一台机器上进行分别导入 save.tar 与 export.tar

    docker load -i save.tar #此时默认导入会导致虚悬镜像
    Loaded image ID: sha256:30a048249000dd36561bffaa9fecc7690a45ef12096849c77afd4543b0d2d9b0
    cat export.tar | docker import - test1:1.0
    sha256:cdd65e7054be9e1928fe8133380817e81ff12b8493b40e2f9106efedac5ee572

    5.查看导入情况

    docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    30a048249000 2 minutes ago 7.37MB
    test1 1.0 cdd65e7054be 3 minutes ago 7.35MB

    6.查看异同

    [root@master-01 ~]$ docker history 30a # save 方式 存在历史层信息

    IMAGE CREATED CREATED BY SIZE COMMENT

    30a048249000 22 minutes ago -d 2 193B create /tmp/demo directory

    2 weeks ago /bin/sh -c #(nop) CMD ["-d" "2"] 0B

    2 weeks ago /bin/sh -c #(nop) ENTRYPOINT ["top" "-b"] 0B

    2 weeks ago /bin/sh -c echo "http://mirrors.huaweicloud".… 1.8MB

    2 weeks ago /bin/sh -c #(nop) MAINTAINER WeiyiGeek mast… 0B

    2 weeks ago /bin/sh -c #(nop) LABEL Author=WeiyiGeek De… 0B

    5 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B

    5 weeks ago /bin/sh -c #(nop) ADD file:c92c248239f8c7b9b… 5.57MB

    [root@master-01 ~]$ docker history cdd # export 方式无历史层信息且只有一层

    IMAGE CREATED CREATED BY SIZE COMMENT

    cdd65e7054be 23 minutes ago 7.35MB Imported from -


2.容器 [Container]

描述:Docker容器Container类似于一个轻量级的沙箱,也可以看做一个简易版的Linux系统环境;因为容器是运行应用程序的,所以必须得先有一个操作系统为基础

Docker利用容器来运行和隔离应用;

  • 容器是从镜像创建的应用独立运行的实例;
  • 可以进行启动/开始/停止/删除容器,而这些容器都是相互隔离,互不可见的;
  • 镜像自身只读的,容器从镜像启动的时候会在镜像的最上层创建一个可写层,镜像本身将保持不变;
  • 创建容器、启动容器、终止容器、进入容器、删除容器、导入导出容器实现容器迁移;

当创建并运行容器时候Docker在后台运行的标准操作包括:

  • 检查本地是否存在指定镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一个可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个IP地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

启动容器有两种方式:

  • 一种是基于镜像新建一个容器并启动
  • 另外一个是将在终止状态(stopped)的容器重新启动。

关键命令:

docker create -it repository[:tag] #创建容器但处于停止状态

docker start -a -i <container id>   #启动创建的容器
# -a 参数   连接容器并打印输出或错误 -
# -i 参数   启动容器并进入交互模式

docker run -t -i repository[:tag]  /bin/bash #创建并启动容器 等同于上面两条命令
# -t:让Docker分配一个伪终端(pseudo-tty)并绑定在容器的标准输入上.
# -i:则让容器的标准输入保持打开.
# CTRL+Z 和 Exit 则退出容器Container
docker run -d repository[:tag] /bin/sh -C "echo hello word" #守护态运行
# -d 参数即可后台运行,用户无法看到容器中的信息
# -c 执行的Command
# --rm 添加这个标记,容器会在停止后立即删除自身   (注意:与-d不能同时使用)
# --name 使用--name web 标记可以为容器重新命名

docker logs <Container-id>      #获取容器的输出信息

docker attach [names]  #采用ps -a NAMES 进入容器

docker exec -it <Container-id>  /bin/bash  #docker exec 至1.3版本起可以在容器中运行命令

docker ps -aq   #显示本机上的所有容器ID运行的容器ID信息

docker restart <container id>  #重启容器

docker stop <container id>  #停止容器
docker kill <cantainer id>  #强行终止容器 可以直接发送SIGKILL信号来终止容器

docker rm <container id>    #删除容器删除依赖该镜像的容器ID,前3位即可
# -f,--force=false 强制终止并删除一个运行中的容器[默认会发生SIGKILL信号]
# -l,--link=false  删除容器连接但保留容器
# -v,--volumes=false  删除容器挂载的数据卷

docker export <container id> >导出文件.tar  #导出容器

docker import - repository[:tag] #导入容器

实例命令:

#创建容器
$sudo docker create -it weiyigeek/czabbix
2b72a3410be576aeb9023ef2c41488e7b2630cf2282b8a0b1dddce771f22231f

#启动容器ID执行交换登录,start可以进入到任何在容器中运行
$sudo docker start -i 2b7
# [root@2b72a3410be5 /]# whoami
# root
# [root@2b72a3410be5 /]# exit
#对于创建后的容器采用exit命令退出后该容器自动处于终止状态;
#提示:正常退出不关闭容器请按Ctrl+P+Q进行退出容器

$sudo docker ps -a  #显示本机上存在的所有容器
# CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                           PORTS               NAMES
# 634e7ed26d76        d131e0fa2585        "/bin/bash"         About an hour ago   Exited (130) About an hour ago                       recursing_goodall
# b72a3410be5        weiyigeek/czabbix   "/bin/bash"         41 seconds ago      Up 21 seconds                           priceless_chaplygin

$docker run -t -i centos /bin/bash  #创建容器并且运行
# [root@0b9aef4876d3 /]# hostname
# 0b9aef4876d3

$ddocker run -d centoszabbix /bin/sh -c "echo Hello World,Docker Container"
4e62768b1d9196fc79fd740c103df79c8e3bb09d9c6810aa43976abeda036a26

$ddocker logs 4e62  #获取容器输出的信息
Hello World,Docker Container

$docker attach priceless_chaplygin  #通过上面的ps -a 中names进入想对应的容器
#当多个窗口同时 attach 到同一个容器的时候 所有窗口都会同步显示 。当某个窗口因命令阻塞时,其他窗口也无法执行操作了
# [root@b72a3410be5 /]$ hostname
# b72a3410be5

$docker exec -ti b72 /bin/echo "whoami"   #可以通过exec直接执行命令和进入shell
# whoami

#停止容器
#当需要删除一个在容器中运行的镜像的时候,会导致不能删除镜像[Image]这时候必须先停止再删除容器[container]:
$sudo docker stop 634e7ed26d76  

#镜像的容器ID,前3位即可,然后在删除镜像
$sudo docker rm 634      #此时再用镜像ID来删除进行,成功则会打印出删除各层信息
$sudo docker rmi -f d131e0fa2585     #镜像的容器ID,前3位即可,然后在删除镜像

#导出镜像
$sudo docker export 092 > czabbix.tar

#导入镜像
$cat czabbix.tar | docker import - test:latest
sha256:bbb9dbcaa5fff9c5b1b99c978df4dcaeeca3d2200d82fc466f5064931bd3bba2
[root@zabbix ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                latest              bbb9dbcaa5ff        14 seconds ago      202MB

补充:使用nsenter工具进入Docker容器

#获取容器的PID
$PID=$(docker inspect --format "{{ .State.Pid}}" <container id>)
#通过得到的这个PID,就可以连接到这个容器:
$nsenter --target $PID --mount --uts --ipc --net --pid

#手动获取docker container 运行的PID
[root@zabbix ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED
092d1b82c6a0        weiyigeek/czabbix   "/bin/bash"         17 minutes ago      ck
[root@zabbix ~]$ docker inspect --format "{{ .State.Pid}}" 092
9030
[root@zabbix ~]$ nsenter --target 9030 --mount --uts --ipc --net --pid
[root@092d1b82c6a0 /]$ cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
#成功进入该容器,9030就是容器092d1b82c6a0的进程PID

Docker load 与 Docker import 的比较
描述:导入容器和导入镜像是差不多的但是实际上又是有所区别的

  • load:导入镜像存储文件到本地镜像库 而 import:导入一个容器快照到本地镜像库
  • 容器快照:文件将丢失所有的历史记录和元数据信息(即保留容器当时的快照状态),导入的时候还需要重新制定标签等元数据信息;
  • 镜像存储:文件将保存完整的记录,并且体积也要大;

总结:

  • 容器的名称是唯一性的如果不指定名称,将会自动生成一个容器名称;
  • 容器是直接提供应用服务的组件,也是Docker实现快速启停和高效服务器性能的基础
  • 在生产环境中因为容器自身的轻量性,建议在容器前段引入HA(高可靠性)机制,当出现错误的时候能快速切换到其他容器之中,还能自动重启故障容器;
3.仓库 [Repository]

描述:Docker 仓库(Repository)类似于代码仓库,是Docker集中存放镜像文件的场所;该概念的引入为Docker镜像文件的分发和管理提供了便捷的途径。
仓库注册地址:https://hub.docker.com/
安装帮助文档:http://www.widuu.com/docker/installation/ubuntu.html

注册服务器是存放仓库的地方,其上往往存放着多个仓库,每个仓库集中存放某一类镜像,往往包括多个镜像文件,通过不同的标签[TAG]来进行区分;

  • 目前最大的公开仓库是 Docker Hub 存放了数量庞大的镜像提供用户下载
  • 国内的公开仓库包括Docker Pool等等可以提供稳定的国内访问。

例如:对于仓库地址dl.dockerpool.com/ubuntu来说,dl.dockerpool.com是注册服务器,ubuntu是仓库名;

Docker 仓库分类:

  • 公开仓库(Public)
  • 私有仓库(Private)

官方与用户提供的镜像比较:

  • 官方:centos 由Docker维护的基础或根镜像;
  • 个人:user/centos 表示由user用户维护的centos基础镜像;

关键点:

docker login   #连接并初始化hub仓库 需要输入用户名和密码认证,然后存放于/root/.docker/config.json 文件中
docker pull [repository]  #下载指定仓库到本地
docker push [imges]

#实例
$docker pull dl.dockerpool.com:5000/ubuntu:12.04 #连接其他的注册服务器
$docker images
$docker tag dl.dockerpool.com:5000/ubuntu:12.04 ubuntu:12.04   #更新标签与官方标签保持一致
$docker push dl.dockerpool.com:5000/ubuntu:12.04  #上传到私有镜像

(1)创建和使用私有仓库:
最快捷还是搭建本地的仓库服务器(注意:配置不当会导致漏洞)

$ docker run -d -p 5000:5000 registry
#默认情况下仓库创建在容器的/tmp/register目录下,-v 通过镜像文件存放在宿主机本地的指定路径/opt/data/registry上;
$ docker run -d -p 5000:5000 -v /opt/data/registry:/tmp/registry registry
# -d:容器启动后会进入后台,用户无法看到容器中的信息.
# -p:指定仓库镜像的端口.
# -v:将镜像的存放位置放在本地指定的路径上.

此时会在本地启动一个私有仓库服务,监听端口为5000; 更新标签本地仓库地址并上传到私有仓库之中:

$docker tag ubuntu:14.04 10.0.2.2:5000/test  #更改镜像的标签
$docker pull 10.0.2.2:5000/test              #下载镜像

#可以以这个来测试是不是存在docker 5000未授权访问漏洞
$ curl http://10.0.2.2:5000/v1/search  #上传成功后可以查看到返回的json字符串

(2)自动创建[Automated Builds]
该功能是对于需要经常升级镜像内程序来说十分的方便,用户创建镜像发生更改的时候进行手动更新镜像,并能通过自动创建功能跟踪目标网络,一旦项目发现新的提交则自动执行创建;配置自动创建步骤如下:

  • 创建登录Docker Hub 绑定Github;
  • 在Docker Hub中配置一个自动创建
  • 选取一个目标网站中的项目(需要Dockerfile和分支)
  • 指定Dockerfile的位置并提交创建,可以在"自动创建页面"跟踪每次创建的状态;

总结:

  • 仓库管理镜像的设计理论与Git差不多,工作流程为文件分发和合作带来的众多优势。

0x03 Docker 数据管理

Data Management,在使用Docker时候必然会在容器内产生数据,或者需要将容器内的数据进行备份,甚至多个容器之间进行数据共享,这时数据管理变得尤为重要;

容器中管理数据的主要有两种方式:

  • 数据卷:Data Volumns
  • 数据卷容器:Data Volume Dontainers

数据管理共享的方式:

  • 使用数据卷容器在容器和主机
  • 容器和容器之间共享数据
1.数据卷

描述:是一个可供容器使用的数据目录,并且让文件系统提供很多有用的特性,数据卷的使用类似于Linux对目录或者文件进行mount操作;

特性:

  • 数据库可以在容器之间共享和重用
  • 数据卷修改后会立马生效
  • 对数据卷的更新不会影响镜像
  • 卷会一直存在,即使容器被删除

Q:如何在容器内创建一个数据卷?

#### 创建一个web容器并创建一个数据卷挂载到容器的/webapp目录下(默认将宿主机/根映射到容器中webapp目录中)
$ sudo docker run -d -P 5000 --name web(新建容器) -v /webapp(容器目录) training/webapp(镜像) python app.py
# --name 指定容器的名称
# -v:将镜像的存放位置放在本地指定的路径上.
# -P:是允许外部访问容器需要暴露的port
# -d:是容器的后台运行守护

#cp 把容器文件copy到宿主机,或者把宿主机的文件copy到容器
$docker cp 容器id或者name:/home/wwwroot/1.php /home/Lcy/ #把容器的1.php拷贝到宿主机家目录
$docker cp config.php 容器id或者name:/home/wwwroot/    #把宿主机的config.php拷贝到容器

挂载主机目录/文件作为数据卷:

#挂载一个主机目录作为数据卷 /src/webapp[主机目录]:/opt/webapp[容器目录]:rw (权限)
$ sudo docker run -d -P --name web -v  /src/webapp:/opt/webapp:rw training/webapp python app.py
# Docker挂载数据卷的默认权限 rw,ro[只读]          //加入ro后数据卷的数据就无法修改了

#挂载一个本地文件作为数据卷(注意再挂载文件得时候尽量设置ro自读,防止inode不一致报错)
$ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash
# --rm :当它退出自动移除容器 即docker ps -aq 不能查询到
2.数据卷容器

描述:IF用户需要在容器之间共享一些持续的数据,最简单的方式就是使用数据卷容器(实际上就是一个普通容器);
使用数据卷容器可以让用户在容器之间自由地升级和移动数据卷;

首先创建一个数据卷容器dbdata并在其中创建一个数据卷挂载到/dbdata上;

$ sudo docker run -it -v /dbdate --name dbdate ubuntu

#然后在容器中进行使用 创建两个容器db1 / db2
$ sudo docker run -it --volumes-from dbdate --name db1 ubuntu
$ sudo docker run -it --volumes-from dbdate --name db2 ubuntu
#使用--volumes-from参数所挂载数据卷的容器自身并不需要保持运行状态

#修改目录中其中任何一个文件,其他容器的该目录都会改变
$ sudo docker run -d --name db2 --volumes-from db1 tarining/postgresql      #可以从已有挂载了容器卷的容器来挂载数据卷
#删除挂载的容器(dbdata 、 db1 、db2)数据卷饼不会被自动的删除,必须在删除最后一个挂载着它容器时显示使用Docker rm -v 命令来指定同时删除关联的容器;

(1) 数据卷容器迁移数据
可以利用数据卷容器对其中的数据卷进行备份、恢复以实现数据的迁移;

#备份: 创建一个worker容器 ,将本地当前目录挂载到容器中backup目录,进行选择数据目录备份压缩
$ sudo docker run --volumes-from dbdata -v $(pwd):/backup --name worker ubuntu tar cvf /backup/backup.tar /dbdate

#恢复: 首先创建一个带有数据卷的容器dbdata2
$ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash
#解压备份文件到挂载的数据卷中
$ sudo docker run --volumes-from dbdata2 $(pwd):/backup busybox tar xvf /backup/backup.tar

实例演示:

#挂载
$docker run --name web --rm -dit -v /opt:/opt/ centos /bin/bash
5b3d35fe3305fb458e8b33f39d5fedfbd7d9cb1f6742bcf725cfbf2ecd0245fc
#进入容器
$docker start -i 5b3
#建立一个文件
[root@5b3d35fe3305 opt]$ vi containerDokcer.txt
#宿主机器目录查看
[root@zabbix opt]$ cat containerDokcer.txt
#!/bin/bash
docker

#挂载单个文件
$docker run --rm -it -v ~/.bash_history:/root/.bash_history centos /bin/bash
$docker rm -vf web  #删除容器和数据卷
$docker ps -a  #由于使用--rm参数则会在容器退出时候删除容器

补充说明::Z与:z的区别

#配置selinux标签如果使用selinux,可以添加z或z选项来修改挂载到容器中的主机文件或目录的selinux标签
#:z选项指示绑定挂载内容在多个容器之间共享。
#:Z选项表示绑定挂载内容是私有和非共享的。
#重要:当使用绑定与服务挂载时,selinux标签(:z和:Z)以及:ro将被忽略,设置了z选项以指定多个容器可以共享绑定挂载的内容,此时不能使用——mount标记修改selinux标签
docker run -d --restart=always --name zhcx-v /disk/webapp/war/:/usr/local/tomcat/webapps:z -p 4081:8080 -e JAVA_OPTS=-Dsome.property=value -e Xmx=1536m tomcat-base:6.0.85-jre8

总结:

  • 推荐直接挂载文件目录到容器中,如果直接挂载一个文件到容器中在使用文本编辑工具时候可能会报错;
  • 可以多次使用--volumes-from参数从来多个容器挂载多个数据卷;锁挂载的容器自身并不需要保持在运行状态
  • 推荐使用数据卷和数据容器之外的物理备份存储系统,如RAID或者分布式系统如Ceph,GPFS,HDFS等

0x04 Docker 网络管理

大量互联网服务包括多个服务组件往往需要多个容器之间进行网络通信相互配合,Docker目前提供了映射容器端口与宿主主机和容器互联机制来为容器提网络服务;并且采用Linux系统知道的网络系统来实现对网络服务的支持使之能提供稳定支持以及快速的高性能转发;

当在一台未经过特殊网络配置的centos 或 ubuntu机器上安装完docker之后,在宿主机上通过ifconfig命令可以看到多了一块名为docker0的网卡;不难想到docker0就不只是一个简单的网卡设备了而是一个网桥。

$ifconfig
docker0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.18.0.1  netmask 255.255.0.0  broadcast 172.18.255.255
$route -n
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0

#下图即为Docker默认网络模式(bridge模式)下的网络环境拓扑图,创建了docker0网桥,并以eth pair连接各容器的网络,容器中的数据通过docker0网桥转发到eth0网卡上。
#网桥(等同于交换机)

#在Linux中,可以使用brctl命令查看和管理网桥(需要安装bridge-utils软件包),比如查看本机上的Linux网桥以及其上的端口:
$yum install bridge-utils
$brctl show
bridge name     bridge id               STP enabled     interfaces
br-63791a62ad5a   8000.024258a66d6e       no        veth4ce6e0e
                                                    vethe5abf0f
br-6cde78afe495   8000.02420a2496c6       no      veth93e8dc0
                                                  vethfda7b14
docker0   8000.024216e63d3c       no

#docker0网桥是在Docker daemon启动时自动创建的,其IP默认为172.18.0.1/16,之后创建的Docker容器都会在docker0子网的范围内选取一个未占用的IP使用,并连接到docker0网桥上。

#除了使用docker0网桥外,还可以使用自己创建的网桥,比如创建一个名为br0的网桥,配置IP:
brctl  addbr br0
ifconfig  br0 18.18.0.1

#在Docker容器和外界通信的过程中,还涉及了数据包在多个网卡间的转发(如从docker0网卡转发到宿主机ens160网卡)
#需要内核将ip-forward功能打开即将ip_forward系统参数设1
echo 1 > /proc/sys/net/ipv4/ip_forward

在Docker在1.9版本中network子命令和跨主机网络支持,为了标准化网络的驱动开发步骤和支持多种网络驱动,Docker公司在libnetwork中使用了CNM(Container Network Model)定义了构建容器虚拟化网络的模型。
libnetwork和Docker daemon及各个网络驱动的关系图:
Docker daemon通过调用libnetwork对外提供的API完成网络的创建和管理等功能,libnetwrok中则使用了CNM来完成网络功能的提供;

CNM中主要有沙盒(sandbox)、端点(endpoint)、网络(network)3种组件:

  • (1)Sandbox:包含了一个容器网络栈的信息,实现对容器的接口、路由和DNS设置等进行管理;
  • (2)Endpoint:它可以加入一个沙盒和一个网络,且一个端点只可以属于一个网络并且只属于一个沙盒;
  • (3)Network:可以直接互相联通的端点(包含多个端点),网络的实现可以是Linux bridge、VLAN等

libnetwork共有5种内置驱动:bridge驱动、host驱动、overlay驱动、remote驱动、null驱动。

  • (1)bridge驱动: Docker默认设置驱动,它可以将创建出来的Docker容器连接到Docker网桥常规方法能满足容器基础需求,然后在复杂场景下使用又诸多限制(使用NAT转发)
  • (2)host驱动:它将不为Docker容器创建网络协议栈(不会创建独立的network namespace),即容器于宿主机共用一个network namespace并使用宿主机的网卡/IP端口等等信息,该驱动适用于对于容器集群规模不大的场景;
  • (3)overlay驱动: 采用IETE标准的VXLAN方式,并且是VXLAN中被普遍认为最适合大规模的云计算虚拟化环境的SDN controller模式,使用时候需要额外的配置存储服务(例如Consul、etcd和zookeeper)
  • (4)remote驱动: 该驱动并未做真正的网络服务实现而是调用了用户自行实现的网络驱动插件,使libnetwork实现了驱动的可插件化,更好地满足了用户的多种需求
  • (5)null驱动: 容器拥有自己的网络命名空间,但并不为Docker容器进行任何网络配置;容器除了network namespace自带的loopback网卡名,没有其他任何网卡、IP、路由等信息(需要用户对齐配置)

libnetwork官方示例:

(1)端口映射实现访问容器
通过-P或者-p来指定端口,

  • 使用-P时候:会进行选择 49000 ~ 49900 端口随机分配映射;
  • 使用-p时候:会让你您设置固定与容器映射的端口;

支持的格式:

  • hostPort:containerPort < 映射到本地指定端口以及容器端口

  • ip:hostPort:containerPort < 映射到本地指定地址以及本地指定端口和容器端口

  • ip::containerPort < 映射本地指定IP的任意端口和容器端口 (注意此处是:

    #随机开启端口
    $sudo docker run -d -P centos python app.py

    #查看转发的端口
    $docker ps -l

    #查看详细的日志信息
    $sudo docker logs -f [NAMES]

    #1.映射所有接口地址 [将本地的5000端口映射到容器的5000端口]
    $sudo docker run -d -p 5000:5000 centos python app.py
    #2.映射指定地址指定端口
    $sudo docker run -d -p 127.0.0.1:5000:5000 centos python app.py
    #3.映射指定地址到的任意端口 还能使用tcp标记来指定udp端口
    $sudo docker run -d -p 127.0.0.1::5000/tcp centos python app.py

    #查看端口映射配置
    $sudo docker port [NAMES] 5000 #容器Port

(2)实现容器间通信
容器的链接(Linking)系统是除了端口映射外的另一种可以与容器中应用进行交换的方式;它会在源和接收容器之间创建一个隧道,接收容器可以看到源容器指定的信息;

Docker两种方式为容器公开连接信息:

  • 1.环境变量

  • 2.更新/etc/hosts

    #首先创建一个新的数据库容器,启动db容器的时候并没有使用-p与-P标记,避免了暴露数据库端口到外部网络上
    $sudo docker run -d --name db tranining/postgres

    #使db容器与web容器建立互联关系;
    #--link name:alias 其中namd是链接的容器的名称,alias是这个链接的别名.
    $ sudo docker run -d -P --name web --link db:db training/webapp python app.py

    #使用 docker ps 来查看容器的连接
    $ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STAT
    US PORTS NAMES
    349169744e49 training/postgres:latest su postgres -c '/usr About a minute ago Up About a minute 5432/tcp db, web/db'
    #web/db这表示web 容器链接到 db 容器,web 容器将被允许访问 db 容器的信息。

    #使用env命令来查看web容器的环境变量
    $sudo docker run --rm --name web1 --link db:db training/webapp env
    #起点汇总DB_开头的环境变量是提供web容器连接到db使用,前缀采用大写的链接别名
    DB_NAME=/web2/db
    DB_PORT=tcp://172.17.0.5:5432
    DB_PORT_5000_TCP=tcp://172.17.0.5:5432
    DB_PORT_5000_TCP_PROTO=tcp
    DB_PORT_5000_TCP_PORT=5432
    DB_PORT_5000_TCP_ADDR=172.17.0.5

    #同时Docker还添加host信息到父容器的/etc/hosts文件
    $sudo docker run -it --rm --link db:db training/webapp /bin/bash
    cat /etc/hosts

实例:

$docker pull nginx
$docker run -d -p 80:80 --name web nginx
$docker container ls -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
16207d8f2291        nginx               "nginx -g 'daemon of…"   19 seconds ago      Up 18 seconds       0.0.0.0:80->80/tcp   web
#容器有自己的内部网络和IP地址,使用docker inspect + 容器ID 可以获取所有的变量值;
$docker inspect web
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"MacAddress": "02:42:ac:12:00:02",

#然后打开80端口进行访问采用logs打印访问
$docker logs -f web
218.x.x.xx - - [08/May/2019:15:00:11 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0" "-"

#进行link关联容器使之能进行正常通信
[root@izwz9biz2m4sd3bb3k38pgz ~]$docker run -d --name web1 --link web:nginx_web alpine cat /etc/hosts
ebe1df8c1fb00462b127d36201d558a9f62507c81faea1ce6c4bf4b5ea6075e3

[root@izwz9biz2m4sd3bb3k38pgz ~]$docker run -d --name web2 --link web:nginx_web alpine env
baa9dfe5f64519eb5ccbd122fc191e0f40118a4ee28385a818f7ffe6e2e03639

[root@izwz9biz2m4sd3bb3k38pgz ~]$docker start -i web1
172.18.0.2      nginx_web 16207d8f2291 web  #成功添加web
172.18.0.3      ebe1df8c1fb0

[root@izwz9biz2m4sd3bb3k38pgz ~]$docker start -i web2
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=baa9dfe5f645
NGINX_WEB_PORT=tcp://172.18.0.2:80  #web2也成功添加web
NGINX_WEB_PORT_80_TCP=tcp://172.18.0.2:80
NGINX_WEB_PORT_80_TCP_ADDR=172.18.0.2
NGINX_WEB_PORT_80_TCP_PORT=80
NGINX_WEB_PORT_80_TCP_PROTO=tcp
NGINX_WEB_NAME=/web2/nginx_web
NGINX_WEB_ENV_NGINX_VERSION=1.15.12-1~stretch
NGINX_WEB_ENV_NJS_VERSION=1.15.12.0.3.1-1~stretch

(3)libnetwork库官方示例
执行流程:

#docker内置的默认默认网卡是是无法使用docker network rm进行删除;
$docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
f26c3ed5f7b0        bridge              bridge              local
fd3572aceb38        host                host                local
01da6464c812        none                null                local

#Step 1.创建名为backend、frontend两个网络
$docker network create backend
6cde78afe495a310bb27c5e1a50074b20e204bfa72e71bcaf6a4c37feb300b93
$docker network create frontend
63791a62ad5a5fe4aeb5926616b2ea1d65b490bb9fb718824b7cb1c408ae50c1
$docker network create -d bridge test-net
#-d:参数指定 Docker 网络类型,有 bridge、overlay/none。

#Step 2. 将c1与c2的容器加入到backend网络中,将c3容器加入到frontend网络中
$docker run -itd --name c1 --net backend alpine
729f2abef71ceaf831999d66264d05f78674d9cd2c235f84481a14b366698adb
$docker run -itd --name c2 --net backend alpine
26d47af2d39a1b00f767c60a68cd5f61f1cf5f48652cdcbcb0216968a3185f5e
$docker run -itd --name c3 --net frontend alpine
9cb94f7c66955ba5a95c90d08ce314da0e477f6eddbcea0329309ec36ca5a711

#Step 3. 分别进入c1和c3容器使用ping命令测试其与c2的连通性,因为c1和c2都在backend网络中,所以两者可以连通。但是因为c3和c2不在一个网络中,所以两个容器之间不能连通:
# 使用docker inspect c3 查看IP信息:
C1: 172.19.0.2
C2: 172.19.0.3
C3: "IPAddress": "172.20.0.2",

# 进入c1容器ping c2通、ping c3不通。其它两个容器就不进入演示了,大家自己可以试一下:
# docker exec -it c1 sh
# ip addr
18: eth0@if19: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:13:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.2/16 brd 172.19.255.255 scope global eth0
# ping c2
PING c2 (172.19.0.3): 56 data bytes
64 bytes from 172.19.0.3: seq=0 ttl=64 time=0.065 ms
64 bytes from 172.19.0.3: seq=1 ttl=64 time=0.050 ms
/ # ping 172.20.0.2
PING 172.20.0.2 (172.20.0.2): 56 data bytes

# Step 4. 将c2连接加入到front网络中,使用exec进入c2中查看网卡信息,测试c2与c3的连通性后,可以发现两者已经连通
$docker network connect frontend c2
$docker exec -it c2 sh
/ $ ip addr
20: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:13:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.19.0.3/16 brd 172.19.255.255 scope global eth0
24: eth1@if25: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:14:00:03 brd ff:ff:ff:ff:ff:ff
    inet 172.20.0.3/16 brd 172.20.255.255 scope global eth1

/ $ ping c3
PING c3 (172.20.0.2): 56 data bytes
64 bytes from 172.20.0.2: seq=0 ttl=64 time=0.100 ms

(4) 跨主机实现互通
通过添加路由路由的方式route

比如:

Docker1: 172.18.0.1/24 --Gateways-- 192.168.1.99

总结:

  • 用户可以链接多个子容器到父容器中比如连接多个web到db容器上;
  • 学习额外的机制比如SDN(软件定义网络)或者NFV(网络功能虚拟化)的相关技术

WeiyiGeek Blog - 为了能到远方,脚下的每一步都不能少。

本文章来源 Blog 站点(友链交换请邮我哟):

更多学习笔记文章请关注 WeiyiGeek 公众账号

点击我关注