什么是Image?
废话不多说,先上图
最底层的Linux Kernel就是宿主机的操作系统内核,这一部分是共享的,称之为boot filesystem。在这之上,是我们的Base Image,是文件和meta data的集合,也称为root filesystem,这一层就是我们的系统镜像,比如Ubuntu,CentOS等,这个镜像只包含操作系统,不包含其它软件。我们在base image之上可以安装一些软件,比如Nginx,PHP等,这会产生新的一层image,如图中image123,都是在系统之上装了一些软件,而image4是在image2的基础上,又装了一些软件,则会产生新一层image。每一层image都是read only的,生成以后就不能改变。
Image获取
那么Image是怎么获取的?在Docker中有个Dockerfile,这个Dockerfile就定义了一个Docker的Image。Dockerfile有自己的语法,后面我们会说到。先来看看一个基本的Dockerfile
FROM ubuntu:16.04 #选择BaseImage
LABEL maintainer="Heheda <heheda@gamil.com>" #作者标识
RUN apt-get update && apt-get install -y redis-server #在BaseImage上运行某个命令
EXPOSE 6379 #暴露端口
ENTRYPOINT [ "/usr/bin/redis-server" ] #程序入口,此处是redis启动入口
通过命令docker build -t your-directory/xxx:latest .
build当前目录下的Dockerfile,你会看到每执行一步,都会生成一个id,一共五个id,对应Dockerfile里的五条语句,每一步都是image的一层(layer),层是可以共享的。以上是手动创建Image的方式。
另一种获取Image的方式是从Docker的Registry中获取。Registry类似docker的github,里面有许许多多的Image供选择。
用docker pull targetImageName
来获取
小提示,如果需要在虚拟机中执行docker命令是不输入sudo,那么需要把当前用户加到docker用户组里。具体命令如下
sudo groupadd docker
sudo gpasswd -a vagrant docker
sudo service docker restart
最后退出账户重新登录,再执行docker命令就不需要sudo了
创建一个简单的BaseImage
- 创建一个目录,里面可以编写一段简单的程序,俗套来了,这里用C语言写个HelloWorld
mkdir hello-world
vim hello.c
用gcc将c文件编译好,得到hello这个二进制可执行文件
gcc -static hello.c -o hello
编写Dockerfile
FROM scratch #因为我们是创建BaseImage,不是安装软件,所以用scratch
ADD hello / #将hello这个可执行文件放在根目录
CMD ["/hello"] #执行根目录下的hello程序
- build image
docker build -t guojh/hello-world .
这条命令 -t是指定image的tag,后面跟的参数就是tag的名字,最后的点表示Dockerfile在当前目录。不加-t也是可以的
可以看到创建过程一共执行了3步,也就是3层,对应Dockerfile里的三条语句。
用docker image ls
查看创建的guojh/hello-world镜像
使用docker history imageID
可以查看image的创建记录。可以看到,我们FROM指定的是scratch,在执行过程中这并不算一层,因为我们创建的是BaseImage。
最后使用docker run guojh/hello-world
执行我们的image
Container
Container是通过Image创建的,会拷贝一份Image。之后会在Image上创建一个Container层,负责读写操作。可以将Image和Container类比成类和实例,Image描述了一个镜像该有的样子,Container根据Image的描述实例化一个镜像。Image在职责上负责存储和分发,Container负责运行。
通过docker run xxx
就是最快的根据Image创建Container的方式。
用docker container ls
或者docker ps
可以查看当前Container,执行命令后我们发现没有任何Container,这是因为我们的hello-world镜像打印出hello, my docker后就退出了,我们可以通过-a参数查看正在运行及已经退出的Container
如何不让Container退出呢?我们需要运行一个有不会退出的进程的image,比如操作系统Image,centos
如果我们直接docker run centos:7
,那么Container依旧会退出。如果不希望推出,可以加-t参数,采用交互式运行
docker run -it centos:7
查询现有Container,可以看到up的Container是centos
如果要删除容器,使用docker container rm ContainerID
或者docker rm ContainerID
如果删除多个镜像,使用docker container rm $(docker container ls -aq)
,-ap是列出所有镜像ID。
如果想删除退出状态的Container,使用docker rm $(docker container ls -f "status=exited" -q)
构建自己的Docker镜像
第一个需要了解的命令docker container commit
,将当前运行的Container变成一个镜像。
首先我们运行前面的centos这个baseImage,运行docker run -it centos:7
这个centos7是不包含vim软件的,我们就在baseImage的基础上,安装一个,运行命令sudo yum install vim
进行安装。
安装完成后,退出centos,运行docker container ls -a
查看当前container。
我们可以看到,红线标出的就是刚才我们退出的container,这个container包含了vim。我们使用
docker container commit
命令来创建Image。这个命令需要两个参数,一个是container的名字,一个是Image的tag,tag默认是latest的,所以我们不用加latest了。最终命令是这样docker container commit epic_stonebraker guojh/centos7-vim
创建完成后,查看image,你会发现多了个我们刚才创建的带VIM的镜像,比baseImage大了100MB+。
这种创建镜像的方式不是特别推荐,因为当发布镜像的时候,只提供了一个image,但具体有没有多加什么其它软件,你并不知道,所以接下来我们说第二种创建Image的方式,用Dockerfile。
我们新建一个目录,docker-centos-vim。进入这个目录后,创建一个Dockerfile,内容如下:
FROM centos
RUN yum install -y vim
然后使用docker build -t your-tag
完成创建。这里有个问题,image既然是只读权限,那么如何在image上安装vim呢?docker不会让自己陷入这个困境的,docker build命令会运行一个临时的container,将baseImage运行起来,然后安装vim,最后生成image。以后我们就可以直接分享Dockerfile给其他人,保证我们的Image不会安装其他恶意软件了。另外提一点,如果希望container在后台运行,加上-t即可。
Dockerfile语法
FROM
FROM后面跟scratch是创建baseImage,FROM centos是使用已有的baseImage,尽量使用官方的baseImage。LABEL
LABEL标签就是增加对镜像的描述,比如LABLE maintainer=xxx
就标识了镜像的持有者。另外还有version,description这些字段。对于LABEL来说,RUN
RUN是执行一些命令,比如安装软件等。这里有一点要注意,每执行一次RUN,便会产生一层Image,为了避免无用分层,尽量用&&
连接命令,如果命令太长,用反斜线来换行。比如
RUN yum install vim && yum install PHP \
yum install Nginx
WORKDIR
这条命令是设定当前工作目录。类似cd命令,如果WORKDIR一个不存在的目录,会自动创建这个目录。这里需要注意,不要使用RUN cd
,这会产生无用层,推荐使用WORKDIR,并且尽量使用绝对路径。ADD and COPY
ADD和COPY都能将本地的文件添加到docker的image中,比如我们之前创建hello镜像的时候。ADD和COPY第一个区别是如果拷贝的是压缩文件,ADD会将压缩文件解压缩后再拷贝,而COPY只是单纯拷贝压缩文件而已。如果只是拷贝,不需要解压缩,还是用COPY。如果是添加远程文件,比如下载一个文件,那么还是RUN来执行wget或者crul吧。ENV
这个关键字是声明常量。比如
ENV MYSQL_VERSION 5.6
RUN apt-get install -y mysql-server="${MYSQL_VERSION}" \
&& rm -rf /var/lib/apt/lists/*
尽量使用ENV增加代码可读性,减少魔数的使用。
- CMD
CMD是容器启动时默认执行的命令,如果定义了多个CMD,只会执行最后一个CMD,如果docker run指定了其它命令,则会忽略CMD。
对于最后一种情况解释一下,比如我们有这样一个Dockerfile:
FROM centos
ENV name Docker
CMD echo "hello, $name"
假设这个Dockerfile生成的image名字是testImage,那么执行docker run tetsImage
后,默认会执行CMD命令,打印hello, Docker。如果执行docker run testImage /bin/bash
,那么就不会打印hello, Docker,因为我们指定了运行/bin/bash。
- ENTRYPOINT
ENTRYPOINT让容易以应用程序或者服务的形式启动,例如启动Nginx这种守护进程。ENTRYPOINT永远不会被忽略,一定会被执行。最好是将要执行的命令写成shell,用ENTRYPOINT来执行这个shell。比如:
COPY docker-entrypoint.sh /usr/local/bin
ENTRYPOINT ["docker-entrypoint.sh"]
- EXPOSE
暴露docker内的端口给外部。
EXPOSE 5000
更多命令可以参考Dockerfile官方文档
Dockerfile
Dockerfile中的格式有两种,一种是Shell格式,一种是Exec格式
Shell:
RUN apt=get install -y vim
CMD echo "finished..."
ENTRYPOINT echo "Hello..."
Exec:
RUN [ "apt-get", "install", "-y", "vim"]
CMD[ "/bin/echo", "finished..."]
ENTRYPOINT ['/bin/echo', "Hello..."]
如果命令中定义了常量,使用常量的时候,Exec格式不会识别常量,CMD可以识别常量。如果必须让Exec识别常量,那么第一个命令应该是/bin/bash
,后面跟上shell命令即可。如
ENV name Heheda
ENTRYPOINT ["/bin/bash", "-c", "echo $name"]
- Dockerfile中执行命令时的动态参数
如果Dockerfile中需要执行一段shell命令,但参数又不确定怎么办?可以使用ENTRYPOINT搭配CMD,如:
ENTRYPOINT ["/usr/bin/bash/ps"]
CMD[]
在运行container的时候,直接跟上需要的参数即可。如:
docker run -it containerID aux | grep parity
这样aux | grep parity
就会被当成ps的参数
更多Dockerfile例子可以在GitHub上搜索docker-library仓库。
搭建自己的Docker镜像仓库
搭建很简单,官方提供了一个搭建仓库的仓库镜像,按照教程来就能搭建好。
关键是创建镜像的时候,tag需要指定为私有仓库的ip+端口。比如我们的仓库地址是12.12.12.12:5000,那么build镜像的时候,tag为12.12.12.12:5000/imagename
。
在push之前,需要在/etc/docker
目录下创建damon.json文件,输入下面的代码:
{
"insecure-registries" : ["12.12.12.12:5000"]
}
把我们的仓库地址被docker信任。
再编辑/lib/systemd/system/docker.service
文件,在文件中增加EnviromentFile=-/etc/docker/daemin.json
最后重启docker
现在我们可以push到私有仓库了,docker push 12.12.12.12:5000/imagename
私有仓库没有提供web界面,可以通过API来检查是否上传成功。
12.12.12.12:5000/v2/_catalog
来查看
容器操作
进入container执行命令或查看container运行的程序
如果希望进入container,可以使用命令docker exec -it containerID shell命令
来实现。比如我想进入container的shell里,那就运行docker exec -it containerID /bin/bash
,这样就能进入container的bash里了。给要运行的容器起名
运行docker run --name=demo imageID
,指定一个名字即可,这个名字是唯一的,不能重复。显示container的详细信息
运行docker inspect containerID
查看container运行log
运行docker logs containerID
容器资源限制
限制container的内存使用量
使用--memory=200M
来限制内存使用。这里指定了内存是200MB,但linux还有交换区内存,默认是和memory指定的大小一致,所以实际的可用内存有400MB。可以使用--memory-swap
来指定交换区内存大小。查看等多命令使用docker run --help
限制CPU使用
这里介绍一个参数--cpu-shares
,这是限制CPU使用权重,以整数表示。比如我container A限制--cpu-share=10
,另一个container B限制--cpu-share=5
,这意味着A的CPU使用率是B的两倍。如果这两个container占用了宿主机全部的CPU资源,那么A对CPU的使用率是B的两倍。
stress压力测试工具
在运行的container里安装好stress后,第一个介绍的命令是--vm,意思是启动进程及给进程分配内存,默认内存是256MB。如果需要多个进程,在命令后面空一格,跟上进程数量,如stress --vm 2
,这里就会创建两个进程。如果要指定进程的内存大小,用--vm-bytes,比如stress --vm 2 --vm-bytes 128M
。如果要查看log,加上--verbose参数。
参考资料
慕课网Docker教程