docker是在13年的样子发布的,我个人第一次听说是在18年,但是去年年底才开始接触,用了一段时间,深得我心,所以题目用得比较过哈哈,遂决定写两篇介绍,不过个人也就用了几个月,经验还是很不足的,如有错误还请指正。
其实网上docker的解说不少,但是很多人使用docker的需求不同,最后写出来的实际应用也就不一样。所以在比较general一点的介绍之后我会根据自己的实际应用举类似专业可能用得到的例子。
docker能做什么?
我就不像许多比较正式的文章说docker结构什么什么的了,就说它的作用。现在这个时代,更新最快的莫过于软件了,毕竟程序猿都这么多了,一会儿就一个新的版本。版本之间还可能不兼容。所以真是一个随时可能被淘汰的行业哈哈。
一般一些
你是否遇到过这种困难,你的电脑上装了软件A的第二版,但是你接到一个项目m,必须用软件A的第一版才能运行,一二版一起装到系统上会冲突,装或者卸载这个软件都很费劲,但是你最终还是卸载了二版,装了一版。没过多久,你又接到一个类似的项目n,必须用软件的二版,于是你只好卸载了一版又装二版。最后你无良的老板让你同时测试项目m和项目n,于是你很栏瘦,很香菇?
稍微专业一些,
现在普遍用python3了,可是有些项目还需要用到python2。你是否需要在他们二者间不断切换?
c++11, 14, 17, 20接踵而至
opencv3.1, 3.4...4都来了
tensorflow的N个1版本和2版本对依赖的要求也不尽相同,软件对tensorflow的版本要求也不尽相同。
CUDA 7,8,9,10十几个版本
在linux系统上运行的软件,有的需要ubuntu16,而有些需要ubuntu18,而最新的ubuntu20 LTS也已经就绪。
你想在mac, windows上使用ubuntu等linux系统
......
上面的东西,如果只需要在其中一个的不同版本之间切换还好,如果需要在众多软件的不同版本间切换,会疯掉的。
面对上面的问题,比较多的人可能会首先想到虚拟机。比如在自己的mac上利用vmware等虚拟开一个ubuntu系统。可是呢,这个假的ubuntu系统会分享你mac的很多运算资源,它无法做到一个独立的系统那样"全力以赴"。对于我们这种对运算要求高的行业,虚拟机是万万不行的。
如果每一个软件的不同版本都去装一个虚拟机,你的电脑也会吃不消的。
这时候docker来救场了,通过docker,你可以在自己的电脑上装不同系统,不同软件的不同版本,运行他们时docker自身几乎不会占用资源。
ubuntu14,ubuntu16,ubuntu18...他们之间互不冲突
你甚至可以装N个ubuntu18,一个ubuntu18里面装tensorflow1.5版本,一个里面装1.6版本,一个里面装2.0版本,装有gpu的版本,装没gpu的版本...只要你的硬盘容量够,你可以装成百上千个,这些都可以同时存在于你的电脑上,并且运行时docker自身几乎不占用资源,运行每一个系统你的电脑都可以全力以赴!任性!
(就是纯粹任性地贴张图,李彦宏没那么说过)
会讲到的几个例子
之后除了非常简要地介绍docker的几个关键词之外,我会根据讲到下面几个例子
1:使用docker跑一个python/c++程序
2:使用docker安装一个比较完整的ubuntu16系统
在讲解时会涉及到一些非常实用的不可不知道的docker命令,让你能尽可能地节省电脑的空间和自己的时间。另外我个人主机使用的是ubuntu18.04所以讲解是基于这个ubuntu讲的,使用windows或者mac的同学在安装docker时应该有所不同,不过在安装好后如何使用docker就基本是一样的了。
docker基本概念
docker最基本的概念是如下两个。
镜像(Image)和容器(Container)
Image的是什么呢?可能有些人之前装机什么的还接触过。说得不准确但直白一些就像一个压缩文件。你如果通过docker下载一个ubuntu系统的镜像,就相当于你获取了一个包含ubuntu系统的压缩文件。
Container是什么呢? 你如果下载了压缩文件,你肯定无法直接操作它(比如里面有个程序,你不能直接跑它),你首先得解压,获得一个文件夹,在这个文件夹里一顿操作。而这个文件夹,就相当于Container了。
也就是说Container和Image的关系就是,Container来源于Image,Container是Image的实现。
好了,基本概念就到这儿,我们可以开始操作了。讲地很简单,第一很多人讲过这个概念,不想重复,第二其他需要补充的地方随着例子来讲解。
docker里的Ubuntu系统
考虑到可能会有同学需要,Windows里对Docker的操作我们之后(可能==)会说。下面的例子皆是在ubuntu系统里操作。要使用docker,首先呢,当然是安装docker了。
这里会有些混乱,首先我们要在已有的系统(ubuntu)里安装一个docker,然后通过docker再安装"docker里的ubuntu系统",这个docker里的ubuntu系统和我们的主系统(称为host)是没有关联的,正如我们在docker能做什么部分里讲到的,我们对这个"docker里的ubuntu系统"的任何操作,都不会影响到我们的主系统。另外, docker是没有图形界面的,你如果之前没用过ubuntu或者相关linux系统,是不建议通过docker来了解ubuntu怎么使用的。
安装docker呢我是一点创新没有,官网怎么说我怎么装(https://docs.docker.com/install/linux/docker-ce/ubuntu/),国内可能安装偏慢。不看英文的百度搜一下ubuntu安装docker
或者windows安装docker
的,一搜一大堆。安装好之后打开一个terminal输入下面的命令
docker run hello-world
如果有下面的输出,就证明你的docker安装好了
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里的ubuntu系统"了。打开一个terminal,在里面输入
docker pull ubuntu:16.04
待这行命令运行结束,ubuntu16.04的镜像就已经在你的docker里装上了,轻松又愉快。接下来我们运行这个docker里的ubuntu系统,在终端中输入下面的命令
docker run -it ubuntu:16.04
(-it什么意思暂时不讲),之前你的terminal左边大概是这样
你输入了
docker run ...
命令之后,等待一两秒,docker就会基于这个镜像运行一个它的容器。你的终端就会变成下面这样这意味着你的用户名变成了root,
root@
后面神奇的数字是你当前容器(docker里的ubuntu系统)的代号。你现在已经在docker里的ubuntu系统了
接着输入一下
ls
,你会看到你会看到显示了一些文件夹的名字,这就是你当初刚安装了一个空白的ubuntu系统时有的那些系统文件夹的名字。
怎么样,从下载这个docker里的ubuntu系统到运行这个系统就两行命令,几秒就能完成,阵是买不了上当买不了吃亏!(国内下载镜像可能会偏慢,本来几秒的事儿。使用阿里云可以加速。great wall使人愁)
这会儿我们就不在这个系统里操作了,输入
exit
可以退出,回到你的主系统
不过这儿先别退出,还有其他的测试。因为上面只是最简demo,让大家知道docker最简单的情况下是怎么工作的,大家也都明白,实际使用和最简demo之间总是有不小的代沟...
接下来讲解更实际的东西。
我们先尝试在上面的"docker里的ubuntu系统"来搞点事情。比如先运行一个python程序吧。我们建立一个新的文件夹并
cd
到里面去。首先呢建立一个python文件
mkdir test
cd test
vim test.py
vim
一般是你通过usb什么的安装ubuntu系统后会自带的文本编辑软件,另一个叫gedit
,不过运行了上面的命令你会收到一个错误信息
bash: vim: command not found
也就是说这个ubuntu系统里没有vim。如果尝试gedit也会收到错误消息
bash: gedit: command not found
你可以通过apt-get下载vim之类的,下载完了之后建立一个python文件运行它,你会发现这个系统里也没有python...
是的,这个系统就是一张纯洁地像白纸一样的系统,是保留了最简化ubuntu系统的内容。这也是会让接触linux系统不久的朋友头疼的原因,什么都得自己装。所以为了达到运行一个python程序的目的,我们一般是怎么做的呢?做的方法一般有两个。先介绍第一个。
一:使用dockerfile为系统预先安装好一些必要软件
我们先使用exit
退出docker回到主系统。大家先跟我执行下面的命令,执行完之后再说明原因。
1:新建立一个文件夹再在里面新建立一个文件名字就叫Dockerfile
mkdir -p docker_tutorial/ubuntu_tutorial
cd docker_tutorial/ubuntu_tutorial
gedit Dockerfile
2:在打开的Dockerfile
里填入下面的内容
From ubuntu:16.04
RUN apt-get update && apt install -y build-essential && apt-get -y install \
python \
vim
保存退出。确保你terminal的路径是Dockerfile
所在的文件夹,在terminal中执行下面的命令
docker build -t my_ubuntu:1 .
之后等待你的terminal会输出很多东西,电脑正在执行上面的命令,terminal执行完后显示的最后一行应该是
...
Successfully tagged my_ubuntu:1
上面的命令,一句话就是通过dockerfile让你基于ubuntu:16.04这个镜像建立了一个新的镜像名叫my_ubuntu:1。
Dockerfile
里的第一行命令
From ubuntu:16.04
表示你要建立的新镜像是基于ubuntu:16.04这个镜像的。From
是Dockerfile的专属关键词。
第二行命令同样开始于Dockerfile的关键词RUN
RUN apt-get update && apt install -y build-essential && apt-get -y install \
python \
vim
RUN
后面所跟的命令就是新的镜像要在原来的镜像基础上添加的内容。想想你在自己的ubuntu系统上如果要更新一下你要输入什么命令? sudo apt-get update
。你如果要下载什么软件你会用什么命令sudo apt-get install some_package
。所以在RUN
之后你就假装自己已经进入了ubuntu系统,平时在terminal中你怎么用命令行装软件的,这而就写什么。从上面的命令你也看出来了有细微的区别,第一没有sudo
了,这原因很简单,你新建立的这个"docker里的ubuntu系统"不像你的主系统有那么高的级别,不需要sudo命令输入密码什么的,默认使用者都是super user了。第二你install后面一定要接-y
的命令,一般你在自己的主系统使用sudo apt-get install some_package
命令后,terminal会让你输入y/n
来表示是否真的要安装,而默认是不安装的(n
)。通过dockerfile来往镜像里装东西,你没有交互的机会,没有-y
,默认软件都不装,你的命令就白输入了。(真是奇怪,默认当然应该是装啊不然我输入命令装软件干什么 = =)。
其余的和你平时用命令行装东西一样了,比如
apt-get update && apt install -y build-essential
表示先执行apt-get update
紧接着再执行apt install -y build-essential
。
apt-get -y install \
python \
vim
等价于
apt-get -y install python && vim
分开几行来写好看些而已。build-essential
包含了gcc g++
等c语言要用到的东西,就先在这儿装上了。总之这儿dockerfile写好后,运行它,我们能得到一个新的镜像,这个镜像包含了基础的ubuntu 16.04以及python和vim了。运行的方式就是terminal中输入
docker build -t my_ubuntu:1 .
(不要忘了最后那个点哦)。其中docker build
就表示要运行这个dockerfile里的内容。-t
后面接新的镜像的名字是my_ubuntu
,1
是这个镜像的标签。我们可能基于同一个镜像建立很多不同的镜像。这时候不需要重新命名一个新的镜像,添加给它不同标签就可以了(就好像github上同一个仓库的不同的branch)。
在新的镜像建立好之后,我们运行这个镜像进入一个它的container
docker run -it my_ubuntu:1
这时候我们就进入了基于我们的新镜像建立的容器里了。这个容器里是应该有python和vim的。在这个容器里我们建立一个test文件夹并用vim建立一个python文件运行它。
输入完
vim
那行命令后terminal会变成vim里的内容,在里面输入我们刚学每一个新语言时的第一个内容
print("hello world!!!")
(如何在vim里输入东西?先点击键盘的i
你就可以输入print("hello world!!!")
了,再点击键盘的Esc
退出输入模式,再按shift :
以及wq
以及确认键保存退出,具体的可随便百度一下vim的基本使用方法)
之后运行该python文件你就得到hello world
了
大功告成???
先exit退出docker一下。
我们还有一些更更实际的东,经常会用到的,稍后再谈。
上面的内容貌似已经够了,但是我在实际使用的时候,需要解决两个(新手时期)很麻烦的问题。我们举个例子引入这两个问题。
假设我们利用python显示一张图片,程序很简单
# importing Image class from PIL package
from PIL import Image
# creating a object
im = Image.open("image_path")
im.show()
那么在docker中,这儿就有个问题了,这个image_path
图像路径应该怎么写?如果现在图片存放在你主机上,docker的container相当于启动了另外一台机子,它只会基于它自己系统的路径寻找。
具体来讲,现在我图片就放在dockerfile同样的路径下,你可以看到有个example_image.png
,我还在主机中
接下来如果我进入docker
我就相当于重新开启了另一台机子。这台机子目前就没有什么
~/tutorial/docker_tutorial...
这个路径,因为这是一个独立的ubuntu系统。那么我们该怎么办呢?我们需要把上面那个example_image.png
拷贝到docker里。具体操作如下,在主机中的dockerfile里加入下面的内容
COPY path_to_image_in_host_machine path_to_copy_the_image_in_docker
就是在docker file里加入一条复制的命令,提供要复制文件在主机的地址和要复制到container里哪个位置,就可以把文件复制进去了。
对我而言就是在之前的dockerfile后面加上一句
COPY example_img.png /home
重新编译下dockerfile
docker build -t my_ubuntu:1 .
这时候在主机那个路径下的文件就被复制到my_ubuntu:1的/home
路径下去了。接下来我们打开一个container瞅瞅
docker run -it my_ubuntu:1
cd /home
ls
就应该能看到那张图片了
这里值得注意的是 dockerfile
COPY
后面跟的主机的文件路径,必须是相对于当前dockerfile的路径,不可给绝对路径。使用COPY命令是绝大多
docker入门讲义
讲到的方法。我们现在只复制了一张图片,如果我们以后要复制一千张一万张图片呢?如果你要使用的数据集有几十上百个G呢?复制的数据是实实在在在你目前的硬盘上占用了两份空间了。所以第一个要解决的问题就是1:如何避免把主机上的数据直接复制到docker里
主机和docker运行的contianer本来是相对独立的,但是我们可以创建一个主机和docker的“共享文件夹”,在这个共享文件夹中放入我们的数据或者任何其他东西。具体操作是使用下面的命令运行docker
docker run -it -v `pwd`:/mnt/shared my_ubuntu:1
主要就是增加了
-v `pwd`:/mnt/shared
这部分命令。在进入docker后,cd到/mnt/shared文件夹,并ls,显示如下
root@ac7248e52355:/# cd /mnt/shared/
root@ac7248e52355:/mnt/shared# ls
Dockerfile example_img.png
你主机当前文件夹的内容,被完完全全的和docker共享了。在主机中重新开启一个terminal,cd到dockerfile所在的文件夹,并在当前文件夹添加一个简单的python文件
Dockerfile example_img.png
zhaozhong@zhaozhong-ThinkPad-X1-Extreme:~/tutorial/docker_tutorial/ubuntu_tutorial$ code hello_world.py
zhaozhong@zhaozhong-ThinkPad-X1-Extreme:~/tutorial/docker_tutorial/ubuntu_tutorial$ ls
Dockerfile example_img.png hello_world.py
我再hello_world.py就写了一行
print("hello_world")
这时候你如果在docker中的/mnt/shared
中ls
自然也就能看到这个python文件
root@ac7248e52355:/mnt/shared# ls
Dockerfile example_img.png hello_world.py
如果当前的docker image中已经安装了python,自然也就可以运行它
root@ac7248e52355:/mnt/shared# python hello_world.py
hello_world
-v
命令可是个大杀器呀,有了它,docker这个黑箱瞬间亮了许多。你其实可以用-v命令共享任何一个主机文件夹和docker container中的文件夹,方式如下
-v 主机文件夹的路径:contianer中文件夹的路径
把上面的命令加入到docker run ...
中去,主机文件夹的内容便被共享到docker里了。
2:在docker中显示UI
在container是不能直接显示任何UI的,因为container自己相当于一个黑箱,黑箱里假设要显示一张图片,你在主机中是看不到的。举个例子,我们写一个python文件来显示图片。稍微用python的同学应该知道我们经常用plt, PIL库来显示图像。但正如我们前面所说的,如果我们的ubuntu16.04之前没有安装pip
,matplotlib
等,得自己装上。ubuntu16.04默认使用的python2,在container中安装相关包命令行如下
apt-get update
apt-get install python-matplotlib
之后创建show_image.py
。你如果在使用docker run
时使用了之前说的
docker run -it -v `pwd`:/mnt/shared my_ubuntu:1
命令,那么这个python文件你从主机的dockerfile所在的地址直接创建或者在docker里的/mnt/shared
文件夹里的路径创建,都可以在docker里运行。下面我直接在主机的当前文件夹中创建这个python文件。
在打开的文件中输入
import matplotlib.pyplot as plt
from PIL import Image
img = Image.open('exmaple_img.png')
plt.imshow(img)
plt.show()
关闭后,在运行当前container里的终端运行该python程序
会得到错误
Traceback (most recent call last):
File "show_img.py", line 5, in <module>
plt.imshow(img)
File "/usr/lib/python2.7/dist-packages/matplotlib/pyplot.py", line 3010, in imshow
ax = gca()
File "/usr/lib/python2.7/dist-packages/matplotlib/pyplot.py", line 928, in gca
return gcf().gca(**kwargs)
File "/usr/lib/python2.7/dist-packages/matplotlib/pyplot.py", line 578, in gcf
return figure()
File "/usr/lib/python2.7/dist-packages/matplotlib/pyplot.py", line 527, in figure
**kwargs)
File "/usr/lib/python2.7/dist-packages/matplotlib/backends/backend_tkagg.py", line 84, in new_figure_manager
return new_figure_manager_given_figure(num, figure)
File "/usr/lib/python2.7/dist-packages/matplotlib/backends/backend_tkagg.py", line 92, in new_figure_manager_given_figure
window = Tk.Tk()
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 1818, in __init__
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
_tkinter.TclError: no display name and no $DISPLAY environment variable
程序本身并没有错,错在docker内不能直接显示图片。如果需要的话,在运行docker时需要增加-e DISPLAY=${DISPLAY} -v /tmp/.X11-unix:/tmp/.X11-unix
命令,我们退出当前的contianer,在docker run时添加命令从新进入
docker run -it -v `pwd`:/mnt/shared -e DISPLAY=${DISPLAY} -v /tmp/.X11-unix:/tmp/.X11-unix my_ubuntu:1
除了上面的操作外,你还需要在主机的终端中输入这样一条命令
xhost +
才能显示UI。
注意我们之前在container里的操作
apt-get update
apt-get install python-matplotlib
在退出contianer后默认不保存的。一定要记住container来源于image,我们使用的imagemy_ubuntu:1
并没有安装python-matplotlib
。image没装,从image再次进入container时这个新的container就没有那个库。但是那个show_img.python
还在的,因为这个文件是保存在主机的文件夹中的,不属于docker管。所以我们得重新装一次matplot库,在container中运行
apt-get update
apt-get install python-matplotlib
当然啦,这个问题肯定可以解决,我们后面说。装好之后,container中运行python文件
python show_img.png
得到结果
完美。
值得注意的是,貌似在mac/windows中,为了显示UI,进入container时需要添加的命令不一定是
-e DISPLAY=${DISPLAY} -v /tmp/.X11-unix:/tmp/.X11-unix
,主机的终端中要输入的命令也不一定是xhost +
,大家自己去查下我就懒得找了= = 。。。关键词答题为 操作系统 docker UI display
,就可以看到相关内容了。避免进入一个image所产生的contianer时重新安装库
方法1: 在docker file中写入安装好相关内容的命令。这是我们在第一部分讲的。比如你要安装update软件包,在dockerfile中写入
RUN apt-get install python-matplotlib
这样在你运行dockerfile(前面docker build ... 那行代码)的时候,所产生的image就会装入上面的软件了。这么做的好处就是你对镜像的操作都被记录了下来,以后别人看你的dockerfile就知道你安装了什么东西。但是坏处就是你一开始不一定知道你在dockerfile里的命令是不是对的,如果运行dockerfile出错,重新运行dockerfile时里面的命令都会重新运行一次。
方法2:这是我个人最常用的方法,使用docker commit命令。
当我们在contianer里运行过一些的命令之后,我们想把改动保存到image里,以便下次docker run该image之后那些改动仍然有效。方法很简单,在主机的terminal中输入
docker commit contianer_id image_name
在你退出当前container下次再进入该image产生的contianer的时候,所有改动仍然有效。例子如下图
root@
后面跟的那串数字就是docker_id,运行了docker commit命令之后,你在上面的container里之前做的任何安装软件之类的操作都会被保存到image里,如果你退出了上图的container,下次通过docker run
命令重新进入一个新的contianer时你在之前container中的改动就仍然对当前container有效了。使用dockerhub
dockerhub和github类似,不过dockerhub是把你建立好的image保存到网上,这样你如果以后用新的电脑,只需要装好docker,从dockerhub里把你需要的image下载下来。比如你有一个在docker中各种东西都装得很完善的ubuntu系统,保存到dockerhub里,入后重新下载下来使用就很方便。
为了使用dockerhub,你需要进入网站注册一个用户。之后就可以把你本地的image保存到dockerhub了。方法就是
docker push dockerhub_usrname/image_name:tag
tag
是image的标记。比如my_ubuntu:1
后面那个1就是tag。日后你如果需要再另一个电脑上或者你重装了系统之类,你只要安装了docker,就可以把上面的image再装回来,命令也很简单
docker pull dockerhub_usrname/image_name:tag
一些小的tips
1:一个container退出的时候,实际上没从系统中删除,会额外占据储存空间。当然有很少的时候你再exit
之后还想进入相同id的contianer,但大多数时候你不必这么做。container里你如果有了什么改动,就像我之前说得使用commit保存下来即可。最好在退出container的时候就删掉它,这么做的方法是在docker run的时候加入--rm
命令,比如我们之前那一长串。
docker run --rm -it -v `pwd`:/mnt/shared -e DISPLAY=${DISPLAY} -v /tmp/.X11-unix:/tmp/.X11-unix my_ubuntu:1
加入了--rm
之后,你exit
当前的container,它也就会被删除
2:上面那命令有点长,但我个人几乎每一次运行docker都用到,所以把他们写进一个.sh
文件运行吧
3:vscode有很好的docker支持。vscode里有专门的docker插件,下载下来后运行vscode它能给你显示你当前的电脑上有那些image,有哪些contianer,其中有哪些在运行,哪些已经停止,方便管理。
上图中绿色三角符号表示我当前在运行的contianer,红色矩形表示停止的。下面还显示了image什么的。你可以直接在vscode里右键contianer/image名字,选择删除停止什么(当然也可以命令行做,自行google/baidu)
总结
懒得总了,终于写完了我去。实际使用docker时,初期还是免除不了不少的疑惑,欢迎私信。最后特别鸣谢扶洋洋博士(优质靠谱单身男青年一枚)在我刚用docker时给予不少帮助。