Docker|字节青训营笔记

198 阅读9分钟

Docker基础概念

Why Docker?

快速,一致性的交付和部署。

docker容器具备高度便携性和可伸缩性。

轻量级且快速。

Docker架构

Docker Daemon(dockerd):监听Docker API的请求,并管理Docker对象(镜像,容器,卷等)。同时也可以和其它Daemon通信。

Docker Client:通过指令与Dockerd进行交互,客户端可以同时与多个Dockerd进行交互。

Docker Registries:Docker仓库,用来存储Docker镜像的仓库,Docker Hub是公开的一个仓库,默认会在Docker Hub上寻找镜像,也可以构建私人镜像仓库。

Docker Objects:使用Docker过程中创建或者与之交互的一些对象:

  • Image:镜像

镜像是一个包含创建Docker容器指令的只读模板。通常一个镜像可以是基于一组镜像的。

借助Dockerfile,用简单的语句来定义创建镜像需要的步骤。Dockerfile中的每一条指令都会在镜像中创建一层。在改变Dockerfile重新build的时候,只会rebuild被改变的层,其它层并不发生改变,这使得镜像比虚拟机更轻量级且快速。

镜像相当于一个Root文件系统,提供容器运行时的程序,库,资源,配置等。

镜像只会存在一个副本,以Image-ID唯一标识,即如果两个镜像一模一样则只会有一个副本

  • Container:容器是独立运行的一个或一组应用以及它们的运行态环境。

容器是镜像运行之后的一个实例,通过Docker API可以run,move,start等,容器也可以与其它网络进行连接。容器默认是与其它容器和主机隔离的,你可以控制容器与主机上其它容器的隔离级别。

容器被移除时,未持久化存储的信息都会丢失,容器的实质是进程,拥有自己的namespace,root文件系统,网络配置,进程空间,甚至用户ID空间。

容器在运行时,会以镜像为基础层,在其上创建一个当前容器的存储层,为容器运行时读写而准备。

Docker最佳实践要求,不要向存储层写入任何数据,所有文件的写入应该写入数据卷Volume或者绑定宿主目录。这些位置的读写会跳过容器存储层没直接对宿主发生读写。数据卷的生命周期长于容器,重启容器之后数据不会消失。

  • 基础技术

Docker基于Go语言开发,使用namespace为Container提供一层隔离,当容器运行时,Docker为容器提供一系列namespace。

Docker使用镜像

获取镜像:默认从docker hub上获取。

镜像是分层存储的,在拉取镜像的过程会一层层去拉取,每次会给出每一层的ID的前十二位,最后会给出镜像的完整sha256摘要,以确保下载一致性。

// 镜像完整名称示例:docker.io/library/ubuntu:18.04
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

列出镜像:镜像ID是唯一的,每一个镜像拥有相同ID,ID相同时表示是同一个镜像(标签可以不同)。

docker image ls			// 查看镜像信息

// 查看镜像、容器、数据卷所占用的空间。
docker system df	
// 列出镜像的全部中间层
docker image ls -a
// 删除虚悬镜像
docker image prune	
// 根据仓仓库名和标签列出镜像
docker image ls <仓库名>:<标签>/仓库名
// 使用过滤器功能,后面加条件:since/before/label等
docker image --filter/-f			
// 查看虚悬镜像
docker image ls -f dangling=true			
// 特定格式展示,只显示ID
docker image ls -q			
// 根据ID删除
docker image rm <ID>		
// 指定格式列出结果
docker image ls --format "{{.ID}}: {{.Repository}}"  /  "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"		
// 镜像摘要 --DIGEST
docker image ls --digest 

虚悬镜像:没有仓库名也没有标签。用表示,可能是新旧镜像同名,旧的镜像名字被取消。也有可能是pull的时候,新的镜像和旧的镜像同名,旧的镜像被覆盖掉了。一般虚悬镜像是可以随意删除的。

删除本地镜像

// 删除本地镜像 image可以是镜像短ID,镜像长ID,镜像名或者镜像摘要digest
docker image rm [选项] <image> <image1>...

删除行为分为Untagged和Deleted两种,在删除某个标签的镜像时,一个镜像可以对应多个标签,所以可能是只是删除了指定标签,还有别的标签指向这个镜像,在这种情况下,Deleted行为就不会发生。当一个镜像的所有标签都被删除光了,此时就会触发Deleted。

镜像是多层结构,所以一般是从上层向基础层方向判断删除。但是很有可能,其它镜像正依赖于某一层,导致删除的时候该层无法被删除。删除的时候应该先删除所有依赖于该镜像的容器。

删除配合ls使用

// 删除所有仓库名为redis的镜像
docker image rm $(docker image ls -q redis)
// 删除所有在mongo:3.2之前的镜像
docker image rm $(docker image ls -q -f before=mongo:3.2)

commit理解镜像

执行docker run时,需要指定镜像,当Docker Hub的镜像无法满足需求时,需要定制镜像。在本地找不到镜像的时候就会去Docker Hub寻找镜像。

// 查看修改日志
docker differ <container-id/name> 

当不使用数据卷时,文件修改是记录在容器的存储层中,此时容器移除之后,数据都会消失。

docker commit可以在初始镜像的基础上,叠加上容器的存储层,形成一个新的镜像,这样就可以将存储层的文件保留在新的镜像中。

docker commit \
	--author "yanghong"\
  --message "修改网页"\
  webserver\
  nginx:v2

docker commit可以直观理解镜像分层的概念,但是一般不这样用,因为一个简单命令的执行都会导致大部分文件的改动,commit会使得镜像十分臃肿。除当前层外,之前的每一层都是不会发生改变的,换句话说,任何修改的结果仅仅是在当前层进行标记、添加、修改,而不会改动上一层。如果使用 docker commit 制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次。

并且除了commit的人员,其他人无法知道镜像是执行了什么操作,因此docker commit导出的镜像被称为黑箱镜像

Dockerfile理解

基于commit的镜像制制作本质上是定制每一层所添加的配置和文件。如果可以将每一层的修改,安装,构建和操作的命令写入一个脚本,使用脚本来构建,则之前黑箱镜像无法重复体积臃肿等问题就能够得到解决。因此Dockerfile的本质就是构建docker镜像的脚本,其内包含了一系列指令,每一条指令构建镜像中的一层

项目路径:/Users/yanghong/Desktop/FDU2021/云原生相关技术/docker

简单编辑Dockerfile文件,就可以在nginx项目的基础上添加一层创建Dockerfile之后就可以build镜像了。

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

From: 指定基础镜像

定制镜像必须以一个镜像为基础,使用From来指定基础镜像,Docker Hub上有许多高质量的官方镜像,比如nginx,redis,mysql,docker等。也有一系列开发环境镜像,比如node,python。scratch是一个特殊的空白镜像,以scratch为镜像说明接下来所写的指令将作为第一层镜像。对于linux下静态编译的程序而言,并不需要操作系统提供运行时的支持,一切库都在可执行文件里,因此Go可以直接From scratch来减小镜像体积。

Run: 执行命令

Run是用来执行命令行命令,命令行具有强大功能,但是每一个Run都会为镜像新增一层。

Run在定制镜像时最常用的指令格式有两种:

  • shell格式 : Run <命令>
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec格式: 类似函数调用的方式
RUN ["可执行文件", "参数1", "参数2"]

Dockerfile支持*""换行#注释*,用*&&连接多条指令*。

在结束之后应该清除无关文件,因为镜像的多层存储机制,每一层的文件并不会在下一层被删除。

Build: 构建镜像

// 打包镜像
docker build [选项] <registry>:<tag> <上下文路径>

Context: 构建上下文

上述的上下文路径不是指Dockerfile指定的路径

Docker build原理:首先Docker在运行时,分为引擎(服务端进程)和客户端工具。Docker引擎提供了一组REST API,被称作Docker Remote API,客户端可以通过调用改组API与Docker引擎进行交互。Docker本质上是一种C/S的架构,本机使用docker的本质还是使用远程过程调用相关API。基于C/S架构,我们在操作远程服务器上的Docker也会变得比较简单。

  • 基于C/S架构,服务端如何获取客户端所指定的本地文件?
    构建镜像的时候,用户会指定构建镜像上下文的路径,docker build会将该路径下的所有内容打包,如果需要忽略某些文件,可以用*.dockerignore*然后上传给docker引擎。例如在Dockerfile中写入:
COPY ./package.json /app/

复制的不是Dockerfile文件下,也不是docker build执行命令的目录下,而是上下文(Context)目录下的package.json

所以下面语句中的* . *是用来指定上下文路径的。

docker build -t nginx:v3 .

Dockerfile一般放在项目根目录或者空目录(也需要指定上下文路径)。习惯性大家会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。

Docker build其它用法

  • 从URL构建,例如从Git Repo中构建
docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world

这行命令指定了构建所需的 Git repo,并且指定分支为 master,构建目录为 /amd64/hello-world/,然后 Docker 就会自己去 git clone 这个项目、切换到指定分支、并进入到指定目录后开始构建。

  • 从tar压缩包构建
docker build http://server/context.tar.gz

如果所给出的 URL 不是个 Git repo,而是个 tar 压缩包,那么 Docker 引擎会下载这个包,并 自动解压缩,以其作为上下文,开始构建。

  • 从标准输入中读取构建
docker build - < Dockerfile
cat Dockerfile | docker build -

如果标准输入传入的是文本文件,则将其视为 Dockerfile,并开始构建。这种形式由于直接从标准输入中读取 Dockerfile 的内容,它没有上下文,因此不可以像其他方法那样可以将本地文件 COPY 进镜像之类的事情。

  • 从标准输入中读取上下文压缩包构建
docker build - < context.tar.gz

如果发现标准输入的文件格式是 gzip、bzip2 以及 xz 的话,将会使其为上下文压缩包,直接将其展开,将里面视为上下文,并开始构建。