docker学习

docker学习

早就在github和学校的机房里见过这个docker了,但是一直不知道是干什么用的,今天正好有空,来学下。

学习视频地址:https://www.bilibili.com/video/av27122140

docker系列技术:Docker+Go+Swarm+Compose+Machine+mesos+k8s+CI/CD+jenkinds整合

这里我们是不学的23333

概念

Docker的意义是什么

解决程序运行环境和配置的问题,这一点除了产品开发和运维、测试的相互协作之外,做科研也很重要的,容器化可以大大降低复现的难度,避免把大量大量的时间浪费在配置环境、安装和卸载软件、处理依赖问题等等没有意义的事情上。现在越来越发现这一点的重要性了,docker解决的正是这个痛点

对于服务器集群,容器也是很有意义的,因为它避免了一台一台安装运行环境的依赖

by the way,这里有一篇对docker安全性进行思考的文章,也提到了其他的容器,以后如果做这方面的话还是不能止于docker啊

代码+配置+系统+数据整体打包

image-20191230211208976.png
Build,Ship and Run Ang App,Anywhere:整体发布、持续集成

和虚拟机的对比

虚拟机启动是分钟级别的,而docker是秒级别的,除此之外,虚拟机资源占用多、冗余步骤多

docker不像虚拟机,它不模拟整套的软件加硬件

image-20191230220930696.png
不需要一整套操作系统,只需要软件所需的资源

另外,容器内的应用直接运行在宿主机的内核上,容器不进行硬件的虚拟,也没有自己的内核,这就使得它相比传统虚拟机轻便不少

但是,容器之间也是相互隔离的,每个容器有自己的文件系统,容器之前的进程不相互影响,能区分计算资源

也就是说,虚拟机是比较彻底的隔离环境,而docker只是应用之间的隔离(这也是它存在安全隐患的地方)

开发自运维:DevOps

弹性云和动态资源调度

image-20191230221635621.png
基础镜像比虚拟机镜像小得多

image-20191230222959278.png
image-20191230221720496.png
Docker对Linux的内核版本要求相比其他软件还是不低的,要注意看自己的环境是否符合要求:主要原因是Docker是用Go开发的,而Go本身出现就比较晚

image-20191230222643993.png
Registry:不自己安装,而是直接从远程仓库拉取

镜像、容器和仓库

镜像可以看为只读的模板,容器是镜像的实例化

image-20191230223104052.png

docker上手

下载与安装

给出Ubuntu在国内使用阿里源的安装方法:

  1. 来源:https://yq.aliyun.com/articles/110806?spm=5176.8351553.0.0.2ae91991v6KFcg,使用centOS或者使用内网安装请到该页面去看
# step 1: 安装必要的一些系统工具
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common
# step 2: 安装GPG证书
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
# Step 3: 写入软件源信息
sudo add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
# Step 4: 更新并安装 Docker-CE
sudo apt-get -y update
sudo apt-get -y install docker-ce
  1. 这篇文章所说,注册账号、获取加速地址:https://cr.console.aliyun.com/cn-beijing/instances/mirrors

    输入该页面中下面的指令即可

  2. 输入sudo docker run hello-world,表示安装正确的返回是:

    Hello from Docker!
    This message shows that your installation appears to be working correctly.
    
    To generate this message, Docker took the following steps:
     1. The Docker client contacted the Docker daemon.
     2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
        (amd64)
     3. The Docker daemon created a new container from that image which runs the
        executable that produces the output you are currently reading.
     4. The Docker daemon streamed that output to the Docker client, which sent it
        to your terminal.
    
    To try something more ambitious, you can run an Ubuntu container with:
     $ docker run -it ubuntu bash
    
    Share images, automate workflows, and more with a free Docker ID:
     https://hub.docker.com/
    
    For more examples and ideas, visit:
     https://docs.docker.com/get-started/
    

    也可以通过docker version来查看版本信息

下载docker镜像可以去docker hub,但是速度你懂得,所以可以使用国内版(eg.阿里云、网易云)

启动docker

启动docker:service docker start

docker run hello-world:运行hello world镜像,先从本地找这个镜像,如果没有的话就从仓库拉取(pull).拉取之后会run

整个过程如下图:

image-20191231002519785.png
注意,docker启动后可能会出现这样的错误:"ERROR: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.40/info: dial unix /var/run/docker.sock: connect: permission denied".这篇文章讲了它的原因,切换为root用户就好了

常用命令

帮助命令

  • docker version
  • docker info:对docker的全面描述
  • docker --help

镜像命令

  • docker images,列出本地所有镜像

    image-20191231201839353.png
    -a:列出所有镜像,包括中间映像层(是的,镜像是嵌套的)

    -q:只显示镜像ID

    -qa:显示包括中间映像层的镜像ID,方便批量删除

    --digests:显示镜像的摘要信息,就是对镜像的说明

    --no-trunc:显示完整的镜像信息

  • docker search <镜像的名称>:从docker hub搜索镜像,我们一般从docker hub查,从阿里云上拉取

    按stars找镜像:docker search -s xx <镜像名称>:只搜索star数大于xx的镜像

    只列出automated build类型的镜像:docker search --automated <镜像名称>

  • docker pull: <镜像名称:镜像的版本号>

    image-20191231203306013.png
    为什么只拉取一个镜像,却出来了这么多?这就是因为镜像是嵌套分层的,下面是不同层的镜像

  • docker rmi <镜像名称>:删除镜像(如果不加版本号,默认删除的是lateset)

    如果要删除的镜像正在使用就不能删,只能强制删除:docker rmi -f <镜像名称/镜像ID>

    也可以删除多个,空格隔开镜像名称或者ID

  • 删除所有镜像:docker rmi -f $(docker images -qa)

    能理解吧应该

容器命令

  1. 新建并启动容器:docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

    image-20191231204819361.png
    交互式运行容器:-it:例如,下载一个操作系统的镜像,然后使用交互式的方式运行,就可以非常方便地进入镜像中进行操作:

    image-20191231205026964.png
    再解释一下这个端口映射:

    例如我们要在docker中运行一个tomcat,我们知道tomcat的默认端口是8080,但是问题是,既然我们使用了容器,我们就可能需要在机器上同时运行几个容器,如果都是tomcat并且使用的都是默认端口的话就会造成端口冲突,所以需要进行端口映射,将内端口8080映射为机器的外部端口

    -P是随机分配端口,可以使用docker ps来查看具体是哪个端口:

    image-20191231232820511.png

  2. 列出docker中所有正在运行的容器:docker ps

    image-20191231205317845.png
    可以看到,一个CONTAINER从一个IMAGE中生成

    UP表示正在运行

    NAMES是容器的名称,没有通过--name指定的话就是随机生成的

    -a:列出所有正在运行的和历史上运行过的容器

    -l:显示最近创建的容器

    -n <数字>:显示最近指定个创建的容器

    -q:静默模式,只显示容器编号

    --no-trunc:不截断输出

  3. 关闭容器:exit,容器停止退出

  4. 暂时离开容器:ctrl+P+Q,容器不停止式退出

    重新进入该容器:

    image-20191231220632534.png
    exec是执行的意思,后面输入bashShell表示执行的对象,也就是新建一个终端,当然,除了输入bashShell,我们也可以输入shell的其他命令,这样就可以执行其他命令了(如果要执行的该命令不是交互式的,也不能能一直保持执行的,那么它就会执行后退出容器,不会停留在容器中)

    也可以输入docker exec -it <容器ID> /bin/bash

  5. 退出之后再启动:docker start <容器名称/ID>

    注意这是启动,而run是创建并启动,换句话说,没有通过run创建的镜像是没办法启动的(其实从理论上也不可能,不创建的镜像你怎么知道名称或者ID呢)

    还可以再重启:docker restart <容器名称/ID>

  6. 停止容器docker stop <容器名称/ID>

  7. 强制停止容器 docker kill <容器名称/ID>

  8. 删除创建了的、但是已经停止的容器:docker rm <容器名称/ID>

  9. 一次性删除所有容器的两个方法:

    • docker rm -f $(docker ps -a -q)

    • docker ps -a -q |xargs docker rm

      这个命令可以好好讲一讲:xargs是linux的可变参数,前面将参数通过管道传递给args.

  10. 以守护式方式启动(也就是后台运行):docker run -d <容器名/ID>

    但其实,这个进程在默认下是会直接退出的,使用docker ps也查不到这些后台命令:

    image-20191231215320490.png
    有一种方法能解决这个问题,就是在后台一直运行一个程序,最常见的就是死循环:

    image-20191231215756280.png
    另外,如果创建的容器是服务器软件,例如tomcat的镜像,也不存在这个问题,因为tomcat在启动之后是一直在监听、运行的,所以效果就死正常在后台运行,连个显示log的终端都不需要

  11. 查看容器日志:docker logs -f -t --tail <数字> 容器ID,参数含义:

    image-20191231215542480.png

  12. 查看容器内运行的进程:docker top <容器名称/ID>

  13. 查看容器内部细节:docker inspect <容器名称/ID>

    以JSON的形式对容器进行描述

  14. 从容器内拷贝文件到主机上:docker cp 容器ID:容器内路径 目标主机路径

  15. docker commit:提交容器副本使之称为一个新的镜像

    docker commit -m="提交的描述信息" -a="作者" 容器ID 要创建的目标镜像名:[标签名]

    可以视为保存自己的配置,保存到的是本地仓库

    除了对自己的镜像进行修改然后commit外,也可以通过dockerfile制作镜像,这两种方法的区别:

    来源:https://www.jianshu.com/p/8d4c4b62bd54

    通过对比显然使用Dockerfile的docker build更好。docker commit的缺点如下:

    \1. 需要在容器内操作麻烦,效率低。

    \2. 这一点也是最重要的,不知道这个镜像是怎么做出来的,都安装了什么。上面我们仅看到增加了94.1M。但是使用Dockerfile我们看到是执行了apt-get install命令

    所以,我们还是应该尽量使用dockerfile来保证对镜像永远是可掌握的状态,而不是渐渐变成一个黑箱

原理

image-20191231005954066.png
通过 ps -ef |grep docker可以查看主机上的docker进程

为什么docker比虚拟机快

image-20191231010139203.png
因为使用的是宿主机的内核,所以新建容器时不需要重新加载操作系统内核

某种程度上,虚拟机面向硬件,而docker面向的是软件

镜像

镜像是什么

image-20191231223905395.png

UnionFS:联合文件系统

image-20191231224019759.png
image-20191231224117279.png

镜像加载原理

image-20191231224139908.png
docker的镜像由一层一层的文件系统组成(这一点和linux的万物皆文件很像)

还有这个系统加载过程也和嵌入式操作系统的加载一样:

来源:https://baike.baidu.com/item/BootLoader

在嵌入式操作系统中,BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。

image-20191231225415308.png
对于不同的linux发行版,bootfs基本上是一致的,rootfs会有差别,由此不同的发行版可以共用bootfs.另外,它也不需要提供kernel,所以可以做到很小

但是这个“小”并不是所有情况下都成立的,例如我们下载一个tomcat的镜像,就有400多M,但是实际上tomcat软件的大小是远小于这个体积的,这就是因为我们下载的不仅仅是一个tomcat软件,还有一整个可以运行tomcat的环境

image-20191231230228316.png
kernel不需要下载,是用宿主机的

这也就是“分层”的各个层的含义

docker采用转载分层结构的原因

image-20191231230442375.png
将所需的服务分层,各个服务之间相同的层可以共享资源

容器数据卷

如果不使用docker commit生成新的镜像,容器中的数据就会随着容器的删除而消失,但有些数据,例如tomcat中的数据,我们是希望把他们持久化保存的。容器数据卷就是为了解决这个问题的,它对容器内的数据进行持久化。

image-20200101000508033.png
数据卷可以在容器间共享和重用资源,它的生命周期一直到没有容器使用它为止

读写

docker run -it -v(v是volume的缩写,表示卷) /宿主机的绝对路径:/容器内目录 镜像名

这样就把容器中的目录保存在了宿主机的路径上(这个路径可以是不存在的,docker会自己创建)

使用docker inspect <容器名/ID>,Volumes下对应的就是容器数据卷在宿主机上的位置

用这种方法,宿主机和容器可以相互通信和共享文件:文件夹内的文件都可以共享

即使容器关闭了,数据也是同步的:也就是说,宿主机在容器关闭时的修改在容器再次启动后依然可见。这其实想想也是符合逻辑的,因为如果不同步的话,文件系统需要维护两个不同版本的文件夹,并且还要相互通信,这很容易造成问题

只读

docker run -it -v /宿主机的绝对路径:/容器内目录:ro 镜像名

其实只是加了一个ro(read only)标记

容器只读取数据卷,不能修改数据卷内容。

使用dockerfile对容器数据卷进行管理

  1. 根目录下新建文件夹并进入:mkdir /mydocker && cd mydocker

  2. 写一个dockerfile,在dockerfile中使用VOLUME指令给镜像添加一个或多个数据卷

    image-20200101105354735.png

    FROM centos
    VOLUME ["/dataVolumeContainer1","/dataVOlumeContainer2"]
    CMD echo "finished"
    CMD /bin/bash
    

    这一段dockerfile翻译为命令的话就是:

    docker run -it -v /host1:/dataVolumeContainer1 -v /host2:/dataVolumenContainer2 centos /bin/bash

  3. build后生成镜像:docker build -f <dockerfile的地址> -t <命名空间/镜像名称> .(这个点表示当前目录)

    image-20200101110228739.png
    生成的新的镜像:

    image-20200101110306576.png

  4. run容器

    这样生成的容器内部就自带了数据卷:

    image-20200101111030556.png
    而在宿主机上的位置是docker在特定目录下随机生成的:

    image-20200101111140692.png
    这样做的好处就是避免了由于人为指定宿主机数据集位置和名称造成的可能的冲突情况,保证了镜像的广泛可用性

容器间传递共享

在run新的容器时加上--volumes-from <已有的容器名称>,就可用使用已有容器的数据卷了。这些容器之间是可用通过数据卷相互传递文件的

数据共享的链是全连接形式的(其实你理解为他们指向同一个宿主机的文件夹),保证任何一个docker容器挂掉,分布式数据还在

dockerfile

概念

dockerfile是用来构建docker镜像的构建文件,是由一系列命令和参数构成的脚本

  1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  2. 指令按照从上到下的顺序执行
  3. 表示注释

  4. 每条指令都会创建一个新的镜像层,并对镜像进行提交

执行dockerfile的大致流程

  1. docker从基础镜像运行一个容器

  2. 执行一条指令并对容器做出修改

  3. 执行类似docker commit的操作提交一个新的镜像层

  4. docker再基于刚提交的镜像运行一个新容器

    一层一层的提交-运行,套娃结构

  5. 执行dockerfile中下一条指令知道所有指令都执行完毕

保留字指令

  1. FROM <基础镜像的名称>:表示当前的镜像是基于哪个镜像的

    FROM scratch(scratch是所有镜像的基础镜像)

    docker hub中99%的镜像都是通过在base镜像中安装和配置需要的软件构建起来的

  2. MAINTAINER:镜像维护者的姓名和邮箱地址

  3. RUN:容器构建时需要的命令

  4. EXPOSE:当前容器对外暴露出的端口号

  5. WORKDIR:指定在创建容器后,终端默认登录进来的工作目录,一个落脚点

  6. ENV:用来在构建镜像过程中设置环境变量

    是在dockerfile中使用,在创建的容器中没有这一条环境变量

  7. ADD:将宿主机的目录下的文件拷贝进镜像且自动处理URL和解压tar压缩包

  8. COPY:类似ADD,但是对URL和tar文件没有特殊处理,只是拷贝而已

    有两种写法:

    1. COPY src dest
    2. COPY ["src","dest"]
  9. CMD:指定一个容器启动时要运行的命令

    dockerfile中可以有多个CMD指令,但是只有最后一个生效

    并且CMD会被docker run之后的参数替换,此时就会有镜像的功能启动失败的风险,也就是如果使用时追加参数,CMD中写的命令就失效了

  10. ENTRYPOINT:指定一个容器启动时要运行的命令

    和CMD类似,但是不是只有最后一个生效,也不会被docker run后手动输入的参数替换掉,而是会追加:docker run之后的参数会被当做参数传递给ENTRYPOINT,之后形成新的命令组合(也就是说,除非用多条shell命令的方式去写,否则ENTRYPOINT里的命令和在使用时输入的命令会合成为一条命令,例如加-i --name等等参数来增强指令)

  11. ONBUILD:当构建一个被继承的dockerfile时,父镜像在被子继承后父镜像的onbuild被触发

案例学习

案例1

  1. 编写一个自己的dockerfile,基于centos,并安装原centos镜像中没有的vim和ifconfig

    from centos
    ENV mypath /home
    WORKDIR $mypath
    RUN yum -y install vim
    RUN yum -y install net-tools
    EXPOSE 80
    CMD /bin/bash
    
  2. build镜像 docker build -f ./mydockerforcentos -t mycentos:1.0 .

    注意,如果我们自己的dockerfile就叫Dockerfile,并且它就放置在该文件夹下且该文件夹下没有重名问题(其实也不可能有重名,因为文件系统不允许重名文件出现在同一个文件夹下),那么可以直接省略-f <dockerfile文件位置>这个部分

  3. 查看docker images,就可以看到这个镜像了

  4. 测试完了可以使用docker rmi -f <镜像ID>删除

案例2

这个案例是用来在centos镜像的基础上自定义tomcat服务器镜像的:

最后这个CMD命令是先启动tomcat再读取日志

这里选择静默运行、不打印日志,并且关联了两个数据卷(本地没有这个目录就创建),这样就能在本地部署项目、看容器的运行结果了

--privileged=true一般没用,除非出现权限不够的情况,算是fix bug吧

同时,由于处于静默运行并且tomcat服务器软件一直在运行,我们可以使用exec与其交互并且不需要担心exec的命令执行完毕后容器关闭的问题。因为exec是打开了新的终端,所以也不需要担心影响原有的tomcat进程:

这个例子其实很有意义的,它表明了我们可以通过在一个linux系统镜像作为基础镜像来配置包含所需功能的镜像

当然,常用的镜像,hub上已经都有了:

案例3:docker常用安装

  1. 安装mysql

    注意运行命令:

输入docker exec -it <容器ID> bin/bash就可进入交互模式对数据库进行管理

  1. 安装redis

    运行命令:

镜像发布

将本地镜像发布(push)到阿里云,当然公司自己有docker服务器的话也可以发布到私有的hub上,发布到阿里云的步骤是:

  1. 登录阿里云容器镜像服务,创建镜像仓库,一个仓库对应一个

  2. 在管理界面就能看到推送的代码:

  1. 登录

  2. 填写相关信息

  3. push

  4. 之后在阿里云hub输入命名空间/镜像名称就能查询到了,自然也就可以通过pull下载了

总结

image-20200101165633014.png

posted @ 2020-01-02 09:00  别再闹了  阅读(527)  评论(0编辑  收藏  举报