Docker镜像的构建

Docker镜像的构建有两种方式,第一种是通过Container来构建,第二种是通过Dockerfile来构建,我们一般推荐通过第二种方式来构建,因为第一种我们将此镜像发不出去,我们是黑盒的,我们不知道它内部是不是有什么不安全的东西在里面,所以推荐使用第二种方式来创建

通过Container构建Docker镜像

在通常的使用中,我们会在已经创建的容器中添加各种文件或操作,这样我们就希望在添加了这些文件或操作后的容器能形成一个新的容器,以便我们移植使用,这个时候我们就可以基于当前已经有的容器来构建一个Docker镜像,以便我们移植。具体步骤如下:

  1. 首先通过命令:docker container ls -a 查询出我们添加了操作的要构建镜像的Container

  2. 通过命令创建新的镜像:docker commit [旧的Container名称] [tag/新的镜像名称]

  3. 通过命令查看我们创建的新的镜像:docker image ls

  4. 通过命令查看容器分层情况:docker history [Container ID],通过此命令查看我们旧的容器和新的容器,发现它们之间共享了很多底层容器,由此可见,我们新的容器是基于旧的容器分层之上创建的

通过Dockerfile来构建镜像

这种方式我们是通过编写Dockerfile来构建镜像,使用命令:Docker build -t [tag/镜像名称]

通过docker image ls,就可以查看到我们新创建的镜像

推荐使用这种方式来构建镜像,因为这种方式我们要给别人这个镜像的时候,我们只需要把Dockerfile共享给其他人就行,而且其他人能看到这个镜像中道理有什么东西,也比较放心

Dockerfile语法

From

From关键字是一般Dockerfile最开头的语句,它指定了我们要build的image它的 Base image 是什么,也就是它在我们哪个Base Image之上来构建我们的Image,示例如下:

From scratch # scratch表示我们不需要任何的Base Image,直接从头来制作的

From centos 或者 From ubuntu:14.04 # 表示我们的Image是基于centos或者ubuntu之上来构建的

LABEL

它定义的是我们的Image的 Mate data,一般Dockerfile中此信息不能少,它是帮助其他人来了解我们的Image的重要信息来源,示例如下:

LABEL maintainer=”jjw-story.china@gmail.com“ # Image的作者

LABEL version=”1.0” # Image的版本

LABEL description=”描述信息“ # Image的描述信息

RUN

RUN就是我们要执行的命令,一般在构建Image时,我们都需要安装一些软件,它的定义是:执行命令并创建新的 Image Layer,这个时候就需要使用RUN来实现,示例如下:

RUN yum update && yum install -y vim \ yum install python

这里有一点需要注意,我们每运行一次RUN,对于我们的Image都会生成新的一层,每生成新的一层,我们在构建的时候就会新生成一层新的Container,然后基于此基础上构建新的Iamge,所以,为了美观,复杂的RUN使用反斜线换行,避免无用分层,合并多条命令成一行,合并多条命令为一行,可以使用 && 符号

WORKDIR

WORKDIR是设定当前工作目录的,这个有点类似在linux中通过cd来改变当前的工作目录,示例如下:

WORKDIR /root

WORKDIR /test
WORKDIR /demo
RUN pwd # 这里会输出的是 /test/demo

我们变更工作目录,尽量使用 WORKDIR,不要使用 RUN cd ,尽量是哟过绝对目录,不要使用相对目录,绝对目录更加清晰

如果我们没有此目录,Docker对自动创建

AND 和 COPY

ADD 和 COPY非常像,都是将本地的文件添加到Image中,使用示例:

ADD test.tar.gz # 会自动解压

WORKDIR /root
ADD demo.tar.gz test/ # 这里会将文件添加到 /root/test/demo 目录下

WORKDIR /root
COPY demo.tar.gz test/ # 这里会将文件添加到 /root/test/demo 目录下

ADD 和 COPY不同的点在于,使用 ADD 会将压缩文件添加并解压掉

大部分情况,COPY由于ADD,添加远程文件/目录,使用 curl 或者 wget 来实现

ENV

ENV作为设置常量或环境变量之用,示例如下:

ENV MYSQL_VERSION 5.6 # 设置常量
RUN apt-get install -y mysql-server=”${MYSQL_VERSION}” # 引用常量

对于我们Dockerfile中上下文中有多处使用此常量,推荐使用ENV,因为我们修改的时候只需要改一次env即可

CMD 和 ENTRYPOINT

CMD:设置容器启动后默认执行的命令和参数

ENTRYPOINT:设置容器启动时运行的命令

Shell格式和Exec格式

首先我们了解两种格式,Shell格式和Exec格式

Shell格式是说把我们要运行的命令当成Shell的格式去运行,第一个是命令,后面的都是命令的参数等,使用示例如下:

1
2
3
RUN apt-get install -y vim
CMD ehco "hello docker"
ENTRYPOINT echo "hello docker"

Exec格式需要通过特定的格式去指明需要运行的命令,以及命令所跟的参数,使用示例如下:

1
2
3
RUN [ "apt-get", "install", "-y", "vim" ]
CMD [ "ehco", "hello docker" ]
ENTRYPOINT [ "echo", "hello docker"]

Exec格式需要指定可执行命令的具体文件

两种方式的区别在于,例如如下示例:

1
2
3
4
5
6
7
# 第一种Dockerfile定义,其他省略
ENV name "docker"
ENTRYPOINT [ "/bin/echo", "hello ${name}"]

# 第二种Dockerfile定义,其他省略
ENV name "docker"
ENTRYPOINT "echo", "hello ${name}"

当我们分别通过两种格式去build Image,并且创建Container时,第一种方式打印出来的是 hello ${name}, 第二种方式打印的是 hello docker,由此可见,第一种Exec的方式它并没有解析ENV的配置,这是因为第二种Shell格式,他会默认铜鼓shell来执行命令echo,而Exec方式,它只是单纯的去执行/bin/echo这个命令,它并没有在shell环境中,所以它解析不了,如何让Exec格式也能解析呢,可以通过如下方法:

1
2
ENV name "docker"
ENTRYPOINT [ "/bin/bash", "-c", "echo hello ${name}"]

这样就可以保证后面的echo命令实在shell环境中执行的,就能正常的解析占位符和变量

这两种方式都可以在RUN CMD ENTRYPOINT中使用

CMD

CMD是容器启动时默认执行的命令,如果docker run指定的其他命令,CMD命令会被忽略,如果定义了多个CMD,只有最后一个会执行,如下示例:

  1. docker run -it jjw-story/hello-docker /bin.bash

如此:我们可以进入容器中,但是并不会打印我们在hello-docker中定义的CMD命令执行的东西,因为,我们在run命令后指定了其他命令, /bin/bash

ENTRYPOINT

让容器以应用程序或者服务的形式与运行,比如说我们启动一个数据的服务,让他作为一个后台进程一直在运行。它不会被忽略,一定会被执行,这是与CMD不同的地方,这里可以写一个shell脚本作为entrypoint启动的脚本

ENTRYPOINT是使用比较广泛的,比CMD应用的多

EXPOSE

我们的容器中很多时候部署的是需要常驻内存的服务,这时我们就需要对外提供端口,此选项就是指定容器对外暴露的端口的,使用方式如下,暴露 5000 的端口

EXPOSE 5000

镜像的发布

很多时候我们自己创建的镜像需要push到我们自己的仓库里以便与别人共享,这时就可以通过早docker hub上创建我们自己的仓库,来把我们创建好的镜像上传上去

  1. 首先需要在Docker Hub上创建我们自己的帐号,登陆上去之后,就可以看到我们自己的仓库,这里面都是我们自己上传的镜像

  2. 在本地找到我们的Docker Image,如果我们要上传此Image,那么我们在build我们自己的Image时,tag一定是我们在Docker Hub上创建的帐号的名称,否则我们在push时,会报没有权限的错误

  3. 本地上传首先我们使用命令在本地登陆Docker Hub,命令: docker login,然后输入我们的用户名和密码

  4. 使用命令上传:docker image push [用户名/Imag名称:tag],注意这里tag一般是latest

  5. push完成后,就可以在Docker Hub上仓库中看到我们上传的Image

  6. 下次我们想下载此Image就可以通过命令:docker pull [用户名/Imag名称] 来下载此Image

上述方式下载的Image比较黑盒,我们不知道这里面究竟有没有其他东西,我们可以通过Docker Hub关联GitHub,将Dockerfile上传到GitHub上,然后关联起来,每次我们上传Dockerfile时,DockerHub会自动的检测到,然后build出新的Image,这样我们在下载时,就可以看到具体的Dockerfile,这里使用时我们自己百度就可以的。

上述方式DockerHub是公有的,所有人都能看到,有些公司私密的我们不需要别人看到,这时我们就需要自己搭建一个私密或者个人的Docker Hub,具体如下:

  1. 在本地使用命令搭建私有的Docker Hub,命令:docker run -d -p [端口号:端口号] –restart always –name regestry registry:2,使用示例如下:docker run -d -p [5000: 5000] –restart always –name regestry registry:2,这样我们就在本地搭建好了一个私有的Docker Hub,我们通过 docker ps 就可以查看此启动的容器

  2. push我们自己的Image,这个时候我们Image的用户名就不能是DOckerHub的用户名了,应该是我们私有的DockerHub的IP和端口,例如:192.168.26.112:5000/hello-world,push命令同上,docker push 192.168.26.112:5000/hello-world,这样就完成了push image到本地私有库。第一次push使用可能会出现错误,因为它会认为我们是不安全的,这里我们通过百度一下来找解决方案,就是创建一个文件加入配置即可

  3. push完成后,下次我们想下载此Image就可以通过命令:docker pull [IP:端口/Imag名称] 来下载此Image

镜像构建问题排查方法

有的时候我们构建一些镜像会出现错误,这时我们就需要通过一些方式来进行具体查看问题原因,例如如下Dockerfile构建示例:

Dockerfile:

1
2
3
4
5
6
7
FROM python:2.7
LABEL maintainer="JJW-STORY"
RUN pip install flask
COPY app.py /app
WORKDIR /app
EXPOSE 5000
CMD ["python", "app.py"]
  1. 首先我们通过命令构建:docker build -t jjw-story/flask-hello-world .

发现发生如下错误:

1
2
3
4
Step 4/7 : COPY app.py /app
---> 89f35be7aac8
Step 5/7 : WORKDIR /app
Cannot mkdir: /app is not a directory

前面说过,docker镜像构建是基于分层的,我们构建时,每一层都会创建一个临时的container,然后基于此container来创建新的image,比如这里出现了此问题,我们就可以通过查看创建的临时container来查看内部具体错误原因

  1. 通过命令查看所有image,找到我们出现错误的上一层创建的临时image,然后基于此image启动容器
1
2
3
4
5
6
7
8
9
10
11
12
13
# 1.查找image,对应上述第四步创建的分层镜像
jjw@jjw-PC:~/AppBoot/flask-hello-world$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 89f35be7aac8 2 minutes ago 906MB
jjw-story/helloworld-java latest 3c378e4f1c1d 8 days ago 682B
jjw-story/hello-docker-c latest 73404f3002a9 11 days ago 756kB

# 2.基于此临时镜像启动容器并使用base进入此容器
~/AppBoot/flask-hello-world$ docker run -it 89f35be7aac8 /bin/bash

# 3.进入容器内部后查看根目录中不存在 /app 文件夹,所以我们定位到了问题
root@e9599f7da1aa:/# ls
app bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
  • 重点命令: docker run -it 89f35be7aac8 /bin/bash
  1. 修改Dcokerfile中的错误,将 COPY app.py /app 修改为 COPY app.py /app/ 此问题即可解决

  2. 重新构建构建镜像,发现已经可以了,并可以启动容器

容器的操作

exec

进入正在运行的docker容器,我们有的时候需要进入正在运行的docker容器中,来查看容器服务运行的状态,日志等,就需要此操作,使用命令: docker exec -it [运行的container ID] /bin/bash

使用示例:

1
2
3
4
5
6
7
8
9
10
11
jjw@jjw-PC:~$ docker exec -it 80e308651aee /bin/bash
root@80e308651aee:/app# ls
app.py
root@80e308651aee:/# ps -ef | grep python
root 1 0 0 14:08 ? 00:00:00 python app.py

root@80e308651aee:/# python
Python 2.7.18 (default, Apr 20 2020, 19:27:10)
[GCC 8.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

stop

停止容器命令: docker container stop [运行的container ID/容器名称] 或者直接 docker stop [运行的container ID/容器名称]

这里可以使用容器名称来停止容器,因为docker中容器名称是唯一的

1
2
3
4
jjw@jjw-PC:~$ docker container stop 80e308651aee
80e308651aee
jjw@jjw-PC:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

run(启动容器指定容器名称)

启动容器并指定容器名称使用 –name 参数来实现,具体命令如下: docker run -d –name=[容器名称] image名称,使用示例:

1
2
3
4
5
6
7
8
9
10
11
jjw@jjw-PC:~$ docker run -d --name=demo jjw-story/flask-hello-world
2e9940a72f76168232e1f9bfbc560c69007a240a4ff85da9fff58e78dde5185b

jjw@jjw-PC:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2e9940a72f76 jjw-story/flask-hello-world "python app.py" 11 seconds ago Up 8 seconds 5000/tcp demo

jw@jjw-PC:~$ docker stop demo
demo
jjw@jjw-PC:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

start(启动已有容器或者说启动被停止的容器)

已有的容器我们可以直接通过名称来启动它,默认就是后台启动,使用命令如下: docker start [容器名称],使用示例:

1
2
3
4
5
jjw@jjw-PC:~$ docker start demo 
demo
jjw@jjw-PC:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2e9940a72f76 jjw-story/flask-hello-world "python app.py" 5 minutes ago Up 3 seconds 5000/tcp demo

inspect(查看容器的详细信息)

可以通过此命令来查看我们启动的或者未启动的容器信息,使用命令如下: docker inspece [容器ID],使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
jjw@jjw-PC:~$ docker inspect 2e9940a72f76
[
{
"Id": "2e9940a72f76168232e1f9bfbc560c69007a240a4ff85da9fff58e78dde5185b",
"Created": "2020-05-07T14:35:16.815307467Z",
"Path": "python",
"Args": [
"app.py"
],
...
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "ce37397299d77a4de06125c0443f242cdad381acb35f4abece69c00e68b63643",
"EndpointID": "5c9e31916c947b9c8ef29452ab80eeb075a7c3f6ca048d669b93848f431902b1",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
}
}
]

可以看到信息非常详细,中间省略,具体可以在使用时查看

logs(查看容器运行的log)

使用命令:docker logs [容器ID],使用示例:

1
2
3
4
5
6
7
jw@jjw-PC:~$ docker logs 2e9940a72f76
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

容器的资源控制

我们可以对容器进行资源限制,虚拟化的技术就是我们一个在一个物理机器上创建多个虚拟机,然后对每个虚拟机进行不同的资源划分,同样对于容器技术来讲,我们的container也是运行在Linux的机器上的,机器的资源也是有限的,如果我们不对容器进行资源限制,那我们创建的这个容器就会尽可能的去占用更多的资源,直到占用满整个机器的内存,所以我们就需要限制容器所占用的资源,比如内存、CPU个数等

具体限制的类型有很多中,我们可以通过: docker run –help 命令来具体查看,这里我们只介绍重要的几种

内存和交换分区的限制

内存和交换分区的限制一般是并行出现的,使用 –memory 和 –memory-swap 参数来指定,需要注意,如果我们只限制 –memory 不限制 –memory-swap,那么默认交换分区的容量和我们限制的内存容量大小是一样的,使用方式如下:

docker run –memory=[自定义大小] –memory-swap=[自定义大小] -d [image名称]

1
2
3
jjw@jjw-PC:~$ docker run --memory=200M -d jjw-story/flask-hello-world
WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.
30f8d4297cc49d1bf7fee1f1d53bad77f3ebfcfc44eafbf0c789515cb814380e

当我们的容器占用内存超过我们的限制,容器就会宕机自动退出

限定CPU资源

CPU资源的限定我们一般使用的是 –cpu-shares 参数,此参数限定的不是说占用CPU的个数,而是一个相对的权重,意思就是:日如我们有两个容器,一个容器我们设置此参数值为10,一个容器我们设置此参数值为5,如果物理CPU已经被占用满了,那么他们其实占用的是一个比例,而且是 2:1 的比例

使用方法: docker run –cpu-shares [image名称]

1
2
3
4
5
6
7
jjw@jjw-PC:~$ docker run --cpu-shares=10 jjw-story/flask-hello-world
* Serving Flask app "app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

最后更新: 2020年05月08日 23:42

原始链接: https://jjw-story.github.io/2020/04/28/Docker镜像的构建和发布/

× 请我吃糖~
打赏二维码