Bootstrap

Docker 学习笔记

建议学习过程

  1. @S_gy_Zetrov–一篇很棒的入门教程
  2. Docker — 从入门到实践–粗略的看下,各个名词部分,进阶部分可以看情况看
  3. @孤天浪雨–Docker系列,建议从第一篇开始看

值得考虑的问题

  1. @傅飞–Docker与虚拟机的区别
  2. @黄庆兵–如何精简压缩image
  3. 精简为王:Docker镜像体积详解
  4. 孤天浪雨–Docker实践(七):Docker Hub(镜像分发、自动化构建)
  5. 还有很多,以后工程上遇到再贴

按照学习的过程我造你肯定走马观花的差不多了,接下来还是仔细的实操一遍吧~

Docker 的一张神图

开局一张图,剩下全靠编~

当大概知晓docker的概念后,看这张图应该会非常的舒服,dockerimage为基础,从下到上构建自己定制的image,一般我们称为image layer,里面可以add各种环境,堆叠。而启动这个image的最小单位为container,一个image可以有启动很多containers,他们相互隔离,可以独立运行,container上做出的改变,比如装个python,如果不进行commitimage,那么退出后不会影响image,当然也不会影响正在运行的其他container了。远端是类似github的一个仓库,用于存放官方和个人镜像,全世界都可以pullpush,进行分发和转送。image的改变可以有两种方式,一是用dockerfile进行build,这是一种类似配置文件的东西,build这个配置文件,命令一条条被执行,简洁快速。而另一种是由container修改后进行commit提交到image,相当于对此修改进行从上到下的”覆盖”,虽然我们对第二种方式更加适应(就相当于操作服务器,而且调试的时候方便),但是对于工程规范严谨来说,建议使用第一种方式,原因我个人认为有三个,一是对日后review来说,有个dockerfile能够更清楚的了解自己构建的image是什么包含什么,二是给别人看的时候方便review,相当于给了个README,三是与github关联进行自动化更新分发image时,docker hub只关心dockerfile的更新。

Image 镜像相关

相关的资料太多了,以下只是在自己学习过程中的例子摆出来方便看

创建自己的新image

一般我们都是从原有的别人的或者官方的镜像也就是image上挂载自己所需要的东西,举一个最简单的例子,Docker hub官方的Ubuntuimage都没有vim的说~

方式1:使用commit进行增量更新

比如我启动一个imagecontainer,然后在这个container上安装一个vim,并把这个拥有vim的增量打包成自己的新image,装python同理

  1. 搜索一下docker hub中的东西~,本质来说就是个类似于github的仓库啦
~> docker search ubuntu  //这里docker支持模糊搜索
NAME                                                   DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
ubuntu                                                 Ubuntu is a Debian-based Linux operating sys…   7198                [OK]
dorowu/ubuntu-desktop-lxde-vnc                         Ubuntu with openssh-server and NoVNC            159                                     [OK]
...
  1. 首先下载一个”底层”image,我们当做基础包,然后再做修改
~> docker pull ubuntu  //这里下的是官方的image,官方的是没有前面那个用户名的,如dorowu/ubuntu-desktop-lxde-vnc表示docker用户名为dorowu的image名叫ubuntu-desktop-lxde-vnc的image
  1. 查看image
~> docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              0458a4468cbc        10 days ago         112MB
  1. *启动该image的一个container***
~> docker run -i -t 0458a4468cbc
root@b42e3c1b7bca:/# vim
bash: vim: command not found
root@b42e3c1b7bca:/# apt-get update && apt-get install -y vim
...

其中,-t 选项让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开,run的作用相当于启动image并且开辟一个容器container,如果本地没有image,那么它会去云端拉一个符合该名字的image

  1. 使用commit进行提交
~> docker commit -m 'install vim' --author='mrlevo' b42e3c1b7bca mrlevo/ubuntuvim:v1
sha256:1ab17ff9221c1dedd419d760135ce87a33a374cc0420590a13345796a3da4af5
~> docker images
REPOSITORY              TAG                 IMAGE ID            CREATED                  SIZE
mrlevo/ubuntuvim        v1                  1ab17ff9221c        Less than a second ago   209MB

其中root后面的b42e3c1b7bca是你这个container唯一的标识符,是一个Hash值,在后续commit的时候需要用到,提交commit的方式和git的方式几乎一致呢,git commit -m 'xxx update',也就是说image会从你在当前container操作当做增量进行image layer的构建

方式2:使用Dockerfile进行build

从这个ubuntu的基础上进行往上添加vim,你把它想做一台虚拟机,你怎么配置环境或者说怎么安装vim呢?apt-get install vim,是不是简单轻松呢~,其实dockerimage也一样,只是为了批量操作,比如说,你有装一堆环境,那么明智的做法肯定是写一个配置文件,然后直接运行这个配置文件让他批处理就行了是吧~,在docker这里,这样的文件叫做Dockerfile*

~/myubuntu> cat Dockerfile  //我已经写好了这个文件,是放在自己的一个叫做myubuntu的文件夹中~
# ubuntu 14.04 with vim and gcc
FROM ubuntu
MAINTAINER mrlevo [email protected]
RUN apt-get update && apt-get -y install vim

具体含义不再解释,一些个参数请直接自己参考Docker Dockerfile详解,这里不再赘述,只是参数而已嘛~大家按照规则写就ok辣,最核心的就是配置文件里写什么也就是这段话中的RUN部分,我们的目的明确,就是装一个vim而已,这里是不是和服务器上装vim一样呢~

然后就开始build吧~

~/myubuntu> docker build -t mrlevo/newubuntu:v2 .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM ubuntu
 ---> 0458a4468cbc
Step 2/3 : MAINTAINER mrlevo [email protected]
...

Successfully built ab33d086befd
Successfully tagged mrlevo/newubuntu:v2

注意mrlevo/newubuntu:v2 .中的.千万别忘了,.Dockerfile 所在的路径(当前目录),也可以替换为一个具体的Dockerfile 的路径。而且为了你以后推到自己的仓库,名字的修改也比较重要,如mrlevo表示DockerHub上的用户名,newubunturepo的名字,v2表示tag号。若在创建docker image的时候。其中-t标记来添加tag,指定新的镜像的用户信息,-t选项不是mrlevo/newubuntu:v2,而是newubuntu:v2,那么即使用mrlevo用户名登录了DockerHub,最后也无法push的。

注意!创建镜像的时候尽量不要用“docker commit”命令来创建。用这种办法建镜像是完全不可取的,因为这种办法是不能重复的。我们在建镜像的时候应该从Dockerfile创建,或者用其他S2I(从源文件构建镜像)的方式来创建,这样镜像才具有可再生性,而且如果我们把镜像存在git之类提供版本控制能的系统里的话,还可以对Dockerfile的改动进行跟踪。

上传自己的image到Docker Hub

先不说这样做明不明智(当然不),只是讲如何将自己的image推到自己的仓库

~> docker push mrlevo/newubuntu:v2
The push refers to repository [docker.io/mrlevo/newubuntu]
ae59a8755490: Pushed
6f4ce6b88849: Pushed
92914665e7f6: Pushed
c98ef191df4b: Pushed
9c7183e0ea88: Pushed
ff986b10a018: Pushed

然后登陆自己的Docker hub上去看看,其实就是和githubpush项目是一样的

现在我们来讨论这样做成本其实很大,其实你只需要让对方拥有你的Dockerfile就可以了,他可以直接下载官方的基础image,然后bulid你的Dockerfile即可重构你的image,没必要把自己的image整个pushdocker hub上(其实还有更骚的操作,就是github上更新dockerfile,然后docker hub上自动化更新image),浪费资源不说,还占带宽~,另一个解决的思路就是压缩image的大小了,参考自@黄庆兵–如何精简压缩image,简单说就是将RUN命令全部写在一起,使用&&串联命令

一个实际的��~ 安装xgboost+python环境

举一个��,我要配置一个含有xgboost这个包的python3环境,其他的都不需要,那么可以简化为在ubuntuimage上,装python以及装第三方包

  1. Dockerfile配置文件,把要做的都写下来,思路可以是自己在操作虚拟机时候的操作,这里先不论是否高效。我先创建本地ubuntuxgboost文件夹,然后再其中创建一个Dockerfile文件,内容如下
~/ubuntxgboost> cat Dockerfile
# ubuntu 14.04 with vim and gcc
FROM ubuntu
MAINTAINER mrlevo [email protected]
RUN apt-get update && apt-get -y install python3 python3-pip
RUN pip3 install pandas scikit-learn==0.18.1 xgboost

当然建议是将RUN的命令用&&串起来,属于一种减少堆叠layer的方法

  1. 进行build
~/ubuntxgboost> docker build -t mrlevo/ubuntuxgboost:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/4 : FROM ubuntu
...
Successfully built 8eddac9d6c4a
Successfully tagged mrlevo/ubuntuxgboost:v1
  1. 查看images
~/ubuntxgboost> docker images
REPOSITORY             TAG                 IMAGE ID            CREATED             SIZE
mrlevo/ubuntuxgboost   v1                  8eddac9d6c4a        3 minutes ago       980MB
  1. 启动image并且开辟一个容器,查看环境是否可用
~/ubuntxgboost> docker run -i -t 8eddac9d6c4a
root@c2061b5db824:/# pip3 freeze
numpy==1.14.0
pandas==0.22.0
python-dateutil==2.6.1
pytz==2017.3
scikit-learn==0.18.1
scipy==1.0.0
six==1.11.0
xgboost==0.7.post3
root@c2061b5db824:/# python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import xgboost
>>>

注:这是一个非常现实的目的,如果需要有一个机器学习平台可供大家直接进行算法测试,那么这个方式就很有用了。别人就不需要在配置各种乱七八糟的环境而头疼了,更专注于实现本质

科学计算的开发环境已经有小伙伴们开源了,这就可以直接用,看,这就是docker的厉害之处~详见:如何使用 Docker 快速配置数据科学开发环境?

Container 容器相关

参考:@孤天浪雨 – Docker实践(二):容器的管理(创建、查看、启动、终止、删除)

若干问题

无法删除image问题

错误描述:unable to remove repository reference

~> docker rmi newubuntu
Error response from daemon: conflict: unable to remove repository reference "newubuntu" (must force) - container 043b9f03b795 is using its referenced image d1ee33a2d62d

首先停止和删除容器,因为一个image上可运行很多container

$ docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }')   //停止容器  
$ docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }')    //删除容器 

$为获取变量,其中grep是全局正则匹配,先是匹配已经退出的container。而awk就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理,这里是获取containerid

然后选择删除指定的image

$ docker rmi d1ee33a2d62d   //删除IMAGE ID=d1ee33a2d62d的image

当然如果你是删掉为none的临时image

$ docker rmi $(docker images | grep "none" | awk '{print $3}')    //删除为none的中间镜像

参考:docker 删除none镜像

PS. 当然如果要删除所有容器,那么只需要 先获取所有容器的CONTAINER ID,然后进行批量删除,注意这里是rm而不是rmi

~> docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS               NAMES
c2061b5db824        8eddac9d6c4a        "/bin/bash"         30 hours ago        Exited (0) 30 hours ago                       vigilant_albattani
~> docker ps -a -q
c2061b5db824
~> docker rm $(docker ps -a -q)
c2061b5db824
~> docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Image 精简压缩

镜像层依赖于一系列的底层技术,比如文件系统(filesystems)、写时复制(copy-on-write)、联合挂载(union mounts)等,详见@黄庆兵–如何精简压缩image

每次在Dockerfile中执行RUN命令的时候系统都会在镜像中新建一个层,每个镜像层都会占用一定的磁盘空间可以使用history观察构建的命令及产生的磁盘空间

~> docker history 885fd3133327
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
885fd3133327        5 hours ago         /bin/sh -c apt-get update && apt-get -y inst…   260MB
<missing>           5 hours ago         /bin/sh -c apt-get update && apt-get -y inst…   97.5MB
<missing>           5 hours ago         /bin/sh -c #(nop)  MAINTAINER mrlevo mrlevo@…   0B
<missing>           2 weeks ago         /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B
<missing>           2 weeks ago         /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B
<missing>           2 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$…   2.76kB
<missing>           2 weeks ago         /bin/sh -c rm -rf /var/lib/apt/lists/*          0B
<missing>           2 weeks ago         /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B
<missing>           2 weeks ago         /bin/sh -c #(nop) ADD file:a3344b835ea6fdc56…   112MB

解决方案总结为以下几点

  • 使用更小的基础image,如使用debian替换ubuntu

  • Dockerfile 中的 RUN指令通过&&\支持将命令串联在一起

  • yum updateapt-get update必须时,把update和清理命令都放在同一行RUN底下,在执行update的同时释放多余的空间
  • 用命令或工具压缩imagedocker 自带的一些命令还能协助压缩镜像,比如 exportimport。工具如docker-squash,用起来更简单方便,并且不会丢失原有镜像的自带信息。

Docker hub 与 Github 的互联,自动化构建image

详细请参考

按照步骤来没什么问题,使用git更新存放在github上dockerfile文件,然后docker hub会根据dockerfile在服务器云端开始构建image,值得注意的是,建议一个工程一个githubrepo,这样方便管理和更新。如果不打上tag进行dockerfile的更新,则把两个版本拉倒本地会出现旧版的tag显示为<none>的情况,而最新版为latest

~> docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
mrlevo/dockerfiles          latest              885fd3133327        16 minutes ago      469MB
mrlevo/dockerfiles          <none>              56fcce54eb10        32 minutes ago      209MB

以下是我更新github上的的dockerfile文件后,docker hub自己关联github并进行image的更新,可以看到dockerfile更新的内容。从Dockerfile可以至少知道这个版本里面是个啥了~,不过貌似我没看到更新日志。看来只能从githubcommit上看了

Docker 镜像的版本控制

参考:@小小工匠–Docker-tag

如果需要升级某个docker镜像,我们可以这样做。

  1. 给每个新生成的镜像都打上相应版本的tag。此时可能存在image:latestimage:v1image:v2等。
  2. 我们要从v1升级到v2,首先我们将导入的v2镜像强制重命名为image:latest,命令为docker tag -f image:v2 image:latest
  3. docker stop之前正在运行的容器
  4. 启用docker run image,此时image的等价镜像image:latest就是最新的V2镜像。

总结下步骤:load/tag/stop/run

致谢

哎呀,越来越懒了,上面超链接的都是哦~谢谢大家的无私贡献才能不断添砖加瓦!

;