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
。