docker容器由浅入深解析

418 阅读11分钟

使用介绍

快速安装

参考官方文档:docs.docker.com/engine/inst…

下面简单介绍下mac的安装方式。

mac安装

image-20210723224732483.png

点击安装即可

最终通过应用程序启动docker image-20210723224831701.png

常用命令

一般情况下我们可以通过docker desktop去配置即可。但是我比较习惯使用了命令行,基操还是可以学习下的。重点介绍几个常用的命令。这里我通过一个小的案例,来介绍下几个常用的命令。

场景:我需要构建一个依赖mysql的应用镜像,并将该利用该镜像启动一个容器运行时。运行启动正常后,停止服务,将该镜像推送到远程仓库。

启动mysql容器

## 拉取mysql镜像,版本为5.7.19,如果需要指定cpu架构,请在命令行中添加--platform linux/amd64
docker pull mysql:5.7.19
## 启动mysql容器,请在命令行中添加--platform linux/amd64
docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7.19

## MAC M1的电脑没有对应arm架构的镜像,建议使用mariadb
docker pull mariadb
## 启动mariadb,并定义数据库的root密码为123456
docker run -p 3306:3306  --name some-mariadb -e MARIADB_ROOT_PASSWORD=123456 -d mariadb
## 查看数据库是否正常
docker ps |grep mariadb

image-20210725114119620.png

构建应用镜像

我们可以在自己代码中添加一个Dockerfile

目录结构如下:

image.png

package main

import (
	"database/sql"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
)

var db *sql.DB //声明一个全局的 db 变量

// 初始化 MySQL 函数
func initMySQL() (err error) {
  // 连接本地数据库,连接数据库test_db
	dsn := "root:123456@tcp(127.0.0.1:3306)/test_db"
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return
	}
	err = db.Ping()
	if err != nil {
		return
	}
	return
}
func main() {
	// 初始化 MySQL
	err := initMySQL()
	if err != nil {
		panic(err)
	}
	defer db.Close()
	fmt.Println("mysql connection is successful")
	select {
	}
}

基于上面的代码,我们对其进行构建 go build -o dockerTest,编辑 Dockerfile如下:

FROM centos
COPY dockerTest /opt/
CMD ["/opt/dockerTest"]
## 构建镜像
docker build -t docker-test .

## 查看镜像
docker images

## 运行该应用,--network是使用本地网络,能够连接上本地启动的mysql服务
docker run -it --rm --network host docker-test

## 应用镜像构建后可以推送到远程仓库,首先将镜像重新打tag,再进行推送
docker tag docker-test xxx.domain.cn/test/docker-test:1.0
docker push docker-test xxx.domain.cn/test/docker-test:1.0

image-20210725213831347-7220313.png

这样我们就完成一个上述场景的操作。

命令介绍

以上的一个操作过程基本涵盖了常用的docker的命令行,这里在附送一个常用命令行分类

命令示例说明
Docker环境信息info、version
容器生命周期管理create、exec、kill、pause、restart、rm、run、 start、stop、unpause
镜像仓库命令login、logout、pull、push、search
镜像管理build、images、import、load、rmi、save、tag、commitbuild,tag,images,rmi一般会比较常用
容器运维操作attach、export、inspect、port、ps、rename、stats、top、wait、cp、diff、updateinspect是查看容器信息比较常用的命令
容器资源管理volume、network
系统日志信息events、history、logs

下图是一张变迁图

bVdlRG.png

如果需要更详细的使用方法,可以参考:www.runoob.com/docker/dock…

常用场景

elasticsearch工具

hub.docker.com/_/kibana

docker run -d --name kibana --network host -p 5601:5601 kibana:6.8.17

redis安装

hub.docker.com/_/redis

docker run -p 6379:6379 --name some-redis -d redis redis-server --appendonly yes

原理介绍

前面讲的都是使用层面的内容。如果作为初学者,可能会有很多疑惑。我当时使用容器的时候,让我最疑惑的点是如下,docker是如何实现将程序构建一次,然后到处能够运行的?那我们就重点聊聊这个问题。

一般我们看到这个问题,第一反应就会想到是容器的镜像机制。镜像负责将程序进行一次封装,无论什么系统,只要部署了docker,将其从镜像仓库拉取下来,我们就可以利用docker将程序运行起来。

镜像原理

镜像定义

Docker镜像是一个只读的Docker容器模板,含有启动Docker容器所需的文件系统结构及其内容,因此是启动一个Docker容器的基础。Docker镜像的文件内容以及一些运行Docker容器的配置组成了Docker容器的静态文件系统运行环境----rootfs。可以这么理解,Docker镜像是Docker容器的静态视角,Docker容器是Docker镜像的运行状态

rootfs

rootfs是Docker容器在启动时内部进程可见的文件系统,即Docker容器的根目录。一般会包含典型的目录系统,如/dev、/proc、/bin、/etc、/lib、/usr、/tmp及运行Docker容器所需的配置文件及工具。镜像其实就是将其镜像的文件内容机器依赖的配置

镜像特点

分层

Docker镜像可以采用分层的方式构建,每个镜像有一系列"镜像层"组成。这样可以实现不同镜像之间共享镜像层的效果。

写时复制

Docker镜像使用了写时复制(copy-on-write)策略,在多个容器之间共享镜像,每个容器在启动的时候并不需要单独复制一份镜像文件,而是将所有镜像层以只读的方式挂载到一个挂载点,再在上面覆盖一个可读写层的容器层。当发生写时,将文件内容写在读写层,并隐藏只读层中的老版本文件。写时复制配合分层机制减少了镜像对磁盘空间的占用和容器启动时间。

内容寻址

Docker镜像的每个镜像层都会进行一个内容计算校验,生成一个内容哈希值,并将该哈希值作为镜像层的唯一标志。该机制提供了镜像的安全性。

联合挂载

联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点内容进行整合,使得最终可见的文件系统将会包含整合之后的各层文件和目录。

Docker镜像的存储组织方式

我们前面提到了镜像的一些特点,那实际上镜像的存储组织方式是怎样的呢?我们可以通过下图来看到一个完整的,在运行的容器的所有文件系统结构情况。

224808y12jsdskqok1s1bt.jpg

镜像相关概念

registry

registry用于保存Docker镜像,其中还包括镜像层次结构和关于镜像的元数据。可以简单将registry想像成类似Git仓库之类的实体。

repository

repository即由具有某个功能的Docker镜像的所有迭代版本构成的镜像组。简单理解,registry是repository的集合,repository是镜像的集合。

manifest

manifest主要存在于registry中作为Docker镜像的元数据文件,在pull、push、save和load中作为镜像结构和基础信息的描述文件。

image和layer

Docker内部的image概念是用来存储一组镜像相关的元数据信息,主要包括镜像的架构(如amd64),镜像默认配置信息,构建镜像的容器配置信息,包含所有镜像层信息的rootfs。Docker利用rootfs中的diff_id计算出内容寻址的索引(ChainID)来获取layer相关信息,进而获取每一个镜像层的文件内容。

layer(镜像层)是一个Docker用来管理镜像的中间概念。Docker镜像管理中的layer主要存放了镜像层的diff_id、size、cache-id和parent等内容,实际的文件内容则是由存储驱动来管理,并可以通过cache-id在本地索引到。

Dockerfile

Dockerfile是允许用户使用基本的DSL语法来定义Docker镜像,每一条指令描述了构建镜像的步骤。当我们使用docker build命令构建自己的Docker镜像时需要使用的定义文件。

docker镜像构建

基于前面的一些相关概念的介绍,我们可以尝试想下它到底是要解决什么问题?为什么要搞这么复杂呢?我直接将其打个tar包不是挺好的?

docker镜像的打包其实是提供了很好的一个交付标准,一般情况下我们只能将涉及代码所依赖的模块一起构建,而镜像可以将操作系统层面的依赖一并打包。而如果要实现这个,将会面临一些列的问题,docker都将其一一解决。

  1. 如何去编排这个构建,docker使用Dockerfile
  2. 如何进行构建,docker build
  3. 如何进行镜像的管理,docker pull,docker push

而这个镜像构建的标准,具体如何实现的呢?

构建实现流程

我们以下命令为例子,来进行分析。

docker build -t xxx:1.0 . 

build的过程主要是针对context打包,context包含即为当前目录。docker服务架构是C/S架构,即客户端和服务端的模式。

客户端会将当前目录进行读取,并将其打包后发送给docker server端,server端接受到请求后做如下动作:

  1. 创建一个临时目录,并将context指定的文件系统解压到该目录下。
  2. 读取并解析Dockerfile。
  3. 根据解析出的Dockerfile遍历其中的所有指令,并分发到不同的模块去执行。
  4. parser为上述每一个指令创建对应的临时容器,在临时容器中执行当前指令,然后通过commit使用此容器生成一个镜像层。
  5. Dockerfile中所有的指令对应的层的结合,就是此次build后的结果。

Docker镜像元数据管理

这里的镜像实体,最终体现也是一个文件系统方式展现。存储管理不同的存储驱动所对应的镜像实现还会有差异。我们将针对目前比较常用的overlay2来进行分析。

repository元数据

前面提到的repository即由具有某个功能的Docker镜像的所有迭代版本构成的镜像库。repository在本地的持久化文件一般存放在/var/lib/docker/images/overlay2/repositoryies.json文件中。

其文件存储了所有repository的名字,每个repository中所有版本镜像的名字和tag已经对应的镜像ID。referenceStore的作用便是解析不同格式的repository名字,并管理repository与镜像ID的映射关系。

image元数据

imageStore则管理镜像ID与镜像元数据之间的映射关系以及元数据的持久化操作,持久化文件一般位于/var/lib/docker/image/overlay2/imagedb/content/sha256/[image_id]

layer元数据

镜像通过一个graph结构管理,每一个镜像层都拥有元数据,记录了该层的构建信息以及父镜像层ID,而最上面的镜像层会多记录一些信息作为整个镜像的元数据。graph则根据镜像ID(即最上层的镜像层ID)和每个镜像层记录的父镜像ID维护了一个树状的镜像层结构。

layer这块的结构稍微有点复杂,我们可以先了解以下几点:

  1. roLayer和mountedLayer,roLayer用与描述不可改变的镜像层,mountedLayer用于描述可读写的容器层。
  2. roLayer里面涉及到两个ID,其文件存放于/var/lib/docker/image/overlay2/layerdb/sha56/[chainID]
    1. diffID采用SHA256算法,基于镜像层文件包的内容计算得到
    2. chainID是基于内容存储的索引,它是根据当前层与所有祖先镜像层diffID计算出来的。
  3. mountedLayer存储的内容主要为索引某个容器的可读写成的ID,容器init层在graphdriver中的ID--initID,读写层在graphdriver中的ID--mountID以及容器层的父镜像层的chainID--parent。持久化文件位于/var/lib/docker/image/overlay2/layerdb/mounts/[container_id]

总结

  1. 简单介绍了docker在mac电脑的安装
  2. 基于实例介绍了docker的使用,常用的命令有build,run,pull,push等
  3. docker镜像的原理主要利用分层机制,写时复制,内容寻址,联合挂载的技术来实现镜像的构建。
  4. 整个构建过程主要是由server端完成,最终构建的产物也就其中放在/var/lib/docker/image下。

结束语

镜像原理大概介绍了,那镜像跟容器又有什么关系呢,容器是如何将程序运行起来的呢?

如果大家觉得内容不错,想一起学习研究IT技术,可以关我的公众号:gungunxi。或者加我的微信号:lcomedy2021

参考资料

官方镜像:github.com/docker-libr…

docker常用管理命令:segmentfault.com/a/119000000…

深入解析docker镜像:linux.cn/article-607…