Docker学习第二篇🌶,这篇我们将学习Docker的镜像和容器以及原理。
先阅读这篇参考文章再阅读这篇笔记:https://mp.weixin.qq.com/s/Ndu17Pa_8Y-RfgqfwqjTSg
Docker的架构和底层技术简介

Docker Platform
首先我们明确一下
Docker提供了一个开发、打包、运行app的平台把
app和底层infrastructure隔离开来

我们从上图看到Docker Engine将底层的物理设备和虚拟设备和上层Application隔离开来,这样我们就可以在Docker之上做一些事情。
Docker Engine
Docker Engine是什么呢?

我们看一下上面的架构图,Docker Engine由下面几个部分组成:
后台进程(dockerd)
REST API Server
CLI接口(docker)
后台进程就是对Docker的镜像,容器,网络,存储进行管理。
Docker 客户端是 Docker 用户与 Docker 交互的主要方式。当您使用 docker 命令行运行命令时, Docker 客户端将这些命令发送给服务器端,服务端将执行这些命令。 docker 命令使用 docker API 。 Docker 客户端可以与多个服务端进行通信。
我们可以在命令行下查看一下:
1 | [root@hongshaorou ~]# docker version |
上面是显示了客户端和服务端的一些信息。
1 | [root@hongshaorou ~]# ps -ef | grep docker |
上面就是守护进程。
Docker Architecture
我们看下Docker的总体架构图

Docker 的核心组件包括:
- Docker Client
- Docker daemon
- Docker Image
- Docker Registry
- Docker Container
Docker 采用的是 Client/Server 架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。客户端和服务器可以运行在同一个 Host 上,客户端也可以通过 socket 或 REST API 与远程的服务器通信。
Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。
而 Linux 容器是 Linux 发展出了另一种虚拟化技术,简单来讲, Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离,相当于是在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样。有了 Docker ,就不用担心环境问题。
Linux对Docker底层技术支持
Namespaces:做隔离pid,net,ipc,mnt,uts
Control groups:做资源限制(对不同容器进行资源管理)
Union file systems:Container和image的分层
底层技术还是依赖于Unix的一些技术。
Docker Image概述
下面是关于Image的一些定义:

不包含内核空间信息 只是用户空间信息
所谓的共享layer就是image4和image2都是使用了底层的Base image。
Docker 镜像可以看作是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。我们可将 Docker 镜像看成只读模板,通过它可以创建 Docker 容器。
镜像有多种生成方法:
- 从无到有开始创建镜像
- 下载并使用别人创建好的现成的镜像
- 在现有镜像上创建新的镜像
我们可以将镜像的内容和创建步骤描述在一个文本文件中,这个文件被称作 Dockerfile ,通过执行 docker build <docker-file> 命令可以构建出 Docker 镜像
1 | [root@hongshaorou ~]# docker image ls |
我们可以使用docker image ls查看当前主机有哪些镜像。
从远程仓库拉取被人创建好的镜像文件
1 | [root@hongshaorou ~]# docker pull ubuntu:14.04 |
我们可以在Docker Hub进行搜索镜像,然后拉取下来:https://hub.docker.com/explore/
上面我们知道Base Image是基于Linux的内核之上为每个Linux发行版做的镜像。
一个小知识,如何在普通用户执行Docker命令的时候不加sudo。
1 | # 新建用户组 |
什么是Container?
下面是关于Container的一些定义:
通过Image创建
在Image Layer之上建立一个container layer(可读写层)
类比面向对象:类(镜像)和实例(镜像)
Image负责app的存储和分发,Container负责运行app

Docker 容器就是 Docker 镜像的运行实例,是真正运行项目程序、消耗系统资源、提供服务的地方。 Docker Container 提供了系统硬件环境,我们可以使用 Docker Images 这些制作好的系统盘,再加上我们所编写好的项目代码, run 一下就可以提供服务啦。
我们看下现在系统中有哪些镜像:
1 | [root@hongshaorou ~]# docker images |
我们运行通过镜像文件来运行一个容器:
1 | [root@hongshaorou ~]# docker run hello-world |
之所以运行完hello-world之后容器立即停止,是因为它不是一个常驻内存的容器。有些容器不会自动终止,因为提供的是服务,比如Mysql镜像等。
如果我们不想立即退出容器,就需要以交互的形式进入容器:
1 | [root@hongshaorou ~]# docker run -it centos |
因为Container是一个读写层,我们可以在这个层进行一个读写操作
1 | [root@d046d1529fc9 /]# yum install vim |
对于已经退出的容器我们可以使用rm删除掉
1 | [root@hongshaorou ~]# docker container ls -a |
但是当退出的数量过多的时候,我们一个一个删有些麻烦。。。。
下面是一些命令
1 | # 查看所有正在运行的docker |
当既存在运行中又存在已退出的容器时候,如果删除全部退出容器呢?
1 | [root@hongshaorou ~]# docker container ls -a |
构建自己的Docker镜像
这个小节我们主要学习两个命令:
1 | [root@hongshaorou ~]# docker container commit |
这个命令可以从一个容器中创建一个镜像,简写docker commit
1 | [root@hongshaorou ~]# docker image build |
这个命令可以从一个Dockerfile中创建一个镜像,简写docker build
我们先学习第一个命令:
1 | # 创建一个容器并进入到交互页面 |
我们看到新的镜像和之前的镜像有重复利用的层,只是在原来的基础镜像上新增了一层。
下面我们看从Dockerfile创建镜像
1 | [root@hongshaorou ~]# mkdir docker-mkdir-vim |
我们看到构建镜像的第一步是基于初始的镜像层,第二步是新建了一个临时容器。然后在临时容器中安装vim安装成功后删容器,最后基于临时的容器commit了一个镜像文件。
查看创建的镜像
1 | [root@hongshaorou docker-mkdir-vim]# docker images |
Dockerfile语法梳理及最佳实践
Dockerfile 中所有的命令都是以下格式:INSTRUCTION argument ,指令 (INSTRUCTION) 不分大小写,但是推荐大写,和sql语句是不是很相似呢?下面我们正式来学习一下这些指令集吧。
FROM
FROM 是用于指定基础的 images ,一般格式为 FROM <image> or FORM <image>:<tag> ,所有的 Dockerfile 都用该以 FROM 开头,FROM 命令指明 Dockerfile 所创建的镜像文件以什么镜像为基础,FROM 以后的所有指令都会在 FROM 的基础上进行创建镜像。可以在同一个 Dockerfile 中多次使用 FROM 命令用于创建多个镜像
1 | FROM scratch # 制作base image |
建议尽量都使用官方的 base image
LABEL
这个指令主要描述了镜像文件的信息如:作者,版本,描述等
1 | LABEL maintainer="hongshaorou@gmail.com" |
MAINTAINER 一般必不可少
WORKDIR
WORKDIR 用于配合 RUN,CMD,ENTRYPOINT 命令设置当前工作路径。可以设置多次,如果是相对路径,则相对前一个 WORKDIR 命令。默认路径为/。一般格式为 WORKDIR /path/to/work/dir。
1 | WORKDIR /test # 如果没有会自动创建test目录 |
用WORKDIR,不要用RUN cd !尽量使用绝对目录!
ADD and COPY
COPY 是用于复制本地主机的 <src> (为 Dockerfile 所在目录的相对路径)到容器中的 <dest>。
当使用本地目录为源目录时,推荐使用 COPY 。一般格式为 COPY <src><dest> 。
ADD也是将本地文件复制到容器中。
ADD和CPOY有一个不同点是不仅可以拷贝还可以解压。
1 | ADD hellp / # 将hello文件复制到容器 |
大部分情况,COPY优先 ADD使用!
ADD除了COPY还有额外功能(解压)!
添加远程文件/目录请使用RUN curl或者wget!
ENV
使用ENV设置常量或环境变量
1 | ENV MYSQL_VERSION 5.6 #设置常量 |
尽量使用ENV 增加可维护性!
EXPOSE
EXPOSE 命令用来指定对外开放的端口。一般格式为 EXPOSE <port> [<port>...]。
1 | EXPOSE 5000 |
VOLUME
后续补充
Shell和Exce格式
我们看下命令的两种格式
Shell格式
1 | RUN apt-get install -y vim |
Exce格式
1 | RUN ["apt-get", "install", "-y", "vim"] |
在Exce情况下是不能直接引用常量的,需要指定Shell环境。而使用Shell格式则会使用默认的Shell环境执行命令。
1 | FROM centos |
RUN
RUN 用于容器内部执行命令创建新的Image Layer。每个 RUN 命令相当于在原有的镜像基础上添加了一个改动层,原有的镜像不会有变化。一般格式为 RUN <command> 。
1 | RUN yum update && yum install -y vim \ |
为了美观,复杂的RUN请用反斜线换行! 避免无用分层,合并多条命令成一行!(使用 &&)
ENTRYPOINT
ENTRYPOINT 设置容器启动时运行的命令,可以让容器以应用程序或者服务的形式运行。
一个 Dockerfile 中只能有一个 ENTRYPOINT,如果有多个,则最后一个生效。
不会被忽略,一定会执行(CMD则可能会被忽略)。
ENTRYPOINT 命令也有两种格式:
ENTRYPOINT ["executable", "param1", "param2"]:推荐使用的exec形式ENTRYPOINT command param1 param2:shell形式
最佳实践:写一个shell脚本作为entrypoint
1 | COPY docker-entrypoint.sh /usr/local/bin/ |
CMD
CMD 设置容器启动后默认指定的命令和参数,CMD 命令可以包含可执行文件,也可以不包含可执行文件。不包含可执行文件的情况下就要用 ENTRYPOINT 指定一个,然后 CMD 命令的参数就会作为ENTRYPOINT的参数。
CMD 命令有三种格式:
CMD ["executable","param1","param2"]:推荐使用的exec形式。CMD ["param1","param2"]:无可执行程序形式CMD command param1 param2:shell 形式。
一个 Dockerfile 中只能有一个CMD,如果有多个,则最后一个生效。而 CMD 的 shell 形式默认调用 /bin/sh -c 执行命令。
如果docker run指定了其他命令,CMD 命令会被 Docker 命令行传入的参数覆盖:docker run busybox /bin/echo Hello Docker 会把 CMD 里的命令覆盖。
设置容器启动后默认指定的命令和参数
Dockerfile官方文档:https://docs.docker.com/engine/reference/builder/#from
官方镜像的Dockerfile参考:https://github.com/docker-library
镜像的发布
Dockerfile 实战
这个小节我们将学习将一个python文件放在容器中运行,并能访问到文件对应的服务。
1 | from flask import Flask |
我们运行上述代码需要python环境和安装flask模块。
下面编写Dockerfile:
1 | FROM python:3.7 # 指定Python3.7为基础镜像 |
通过Dockerfile创建镜像:
1 | [root@hongshaorou flask-hello]# docker build -t flask-hell . |
构建镜像的临时镜像 我们是可以 运行的
运行容器:
1 | [root@hongshaorou flask-hello]# docker images |
容器的操作
我们上面创建了一个容器,现在我们学习一些关于容器的操作。
1 | # 查看运行中的容器 |
命令的官方文档:https://docs.docker.com/engine/reference/commandline/container/
Dockerfile的另一种实践
上面我们使用Dockerfile生成了常驻内存的Docker容器。还可以生成一直具有交互式设计的容器。
1 | FROM ubuntu |
1 | # 通过在docker run的时候 可以传递参数给 Docker file中空的CMD |
容器的资源限制
我们在启动容器的时候,可以对使用资源进行一些限制。
当我们新建一个容器的时候,如果没有限制资源的话将会无限制使用主机资源。
1 | -m, --memory bytes Memory limit |
我们通过--memory指定内存大小,--memory-swap指定swa大小。如果没有指定swap就是和memory一样大小。
1 | # 限制内存使用 |
1 | -c, --cpu-shares int CPU shares (relative weight) |
CPU设置限制不是个数,是相对权重。
1 | docker run --cpu-shares=10 |
当有多个容器的时候 是相对权重可以类别NGINX的负载均相对权重
容器的资源限制,底层技术就是Control groups。
容器的文件系统,底层技术是Namespaces。
容器的分层,底层技术是Union file systems。