当前位置 : 首页 » 文章分类 :  开发  »  Docker

Docker

Docker 学习笔记

Docker 是一个跨平台、可移植并且简单易用的容器解决方案。基于 Go 语言开发。
Docker 可在容器内部快速自动化地部署应用,并通过操作系统内核技术(namespaces、cgroup等)为容器提供资源隔离和安全保障。

《Docker — 从入门到实践》 – docker 中文白皮书
https://yeasy.gitbooks.io/docker_practice/content/
https://github.com/yeasy/docker_practice

learn to build and deploy your distributed applications easily to the cloud with docker
https://docker-curriculum.com/


基本概念

为什么要用docker?

一方面应用包含多种服务,这些服务有自己所依赖的库和软件包;另一方面存在多种部署环境,服务在运行时可能需要动态迁移到不同环境中。这就产生了一个问题:
如何让每种服务能够在所有的部署环境中顺利进行?
各种服务与环境排列组合产生了一个大矩阵,开发人员需要考虑不同的运行环境,运维人员需要为不同的服务和平台配置环境。这对双方而言,都是一项艰难的任务。
如何解决这个问题呢?

最终程序员们从传统运输业找到了答案。

几十年前,运输业面临着类似的问题。
每一次运输,货主与承运方都会担心因货物类型的不同而导致损失,比如几个铁桶错误地压在了一堆香蕉上。另一方面,运输过程中需要使用不同的交通工具也让整个过程痛苦不堪:货物先装上车运到码头,卸货,然后装上船,到岸后又卸下船,再装上火车,到达目的地,最后卸货。一半以上的时间花费在装、卸货上,而且搬上搬下还容易损坏货物。 这也是一个N x M 的矩阵。

集装箱的发明解决这个难题。
任何货物,无论钢琴还是保时捷,都被放到各自的集装箱中。集装箱在整个运输过程中都是密封的,只有到达最终目的地才被打开。标准集装箱可以被高效地装卸、重叠和长途运输。现代化的起重机可以自动在卡车、轮船和火车之间移动集装箱。集装箱被誉为运输业与世界贸易最重要的发明。

Docker 将集装箱思想运用到软件打包上,为代码提供了一个基于容器的标准化运输系统。
Docker 可以将任何应用及其依赖打包成一个轻量级、可移植、自包含的容器。容器可以运行在几乎所有的操作系统上。


repository 仓库

镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。

一个 Docker Registry 中可以包含多个 仓库(Repository);每个仓库可以包含多个 标签(Tag);每个标签对应一个镜像。

通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 仓库名:标签 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。

以 Ubuntu 镜像 为例,ubuntu 是仓库的名字,其内包含有不同的版本标签,如,16.04, 18.04。我们可以通过 ubuntu:16.04,或者 ubuntu:18.04 来具体指定所需哪个版本的镜像。如果忽略了标签,比如 ubuntu,那将视为 ubuntu:latest

image 镜像

Docker 的镜像概念类似于虚拟机里的镜像,是一个只读的模板,一个独立的文件系统,包括运行容器所需的数据,可以用来创建新的容器。
docker 镜像是一个只读的docker容器模板,它含有启动docker 容器所需的文件系统结构及内容,是启动一个docker 容器的基础。
镜像(Image)是 Docker 最突出的创新。

镜像是容器的基础,每次执行 docker run 的时候都会指定哪个镜像作为容器运行的基础。

docker镜像的文件内容以及一些运行docker 容器的配置文件组成了docker 容器的静态文件系统运行环境 ——— rootfs。

rootfs 是docker 容器在启动时内部进程可见的文件系统,即 docker 容器的根目录。rootfs 通常包含一个操作系统运行时所需的文件系统。

在传统的linux 操作系统内核启动时,首先挂载一个只读的rootfs,当系统检测其完整性以后,再将其切换为读写模式。在docker daemon 为docker 容器挂载rootfs 时,沿用了上述方法,即将rootfs 设为只读模式,挂载完毕后,利用联合挂载(union mount)技术在已有的只读rootfs 上在挂载一个读写层。

镜像的特点:
分层:docker 镜像是采用分层方式构建的,每个镜像都由一系列的“镜像层”组成
写时复制:每个容器启动时不需要单独复制一份镜像文件,而是将所有镜像层以只读的方式挂载到一个挂载点,在其上覆盖一个可读写的容器层。
内容寻址:对镜像内容计算校验和,生成内容哈希,减少冲突。
联合挂载:联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载的内容进行整合,使得最终可见的文件系统将会包含整合之后的各层的文件和目录。

image 镜像内容

将一个镜像导出为 tar 文件再解压后,其中的文件如下

$ docker save -o hello-world.tar hello-world:latest
$ tar -xvf hello-world.tar -C hello-world-image
cdccdf50922d90e847e097347de49119be0f17c18b4a2d98da9919fa5884479d/
cdccdf50922d90e847e097347de49119be0f17c18b4a2d98da9919fa5884479d/VERSION
cdccdf50922d90e847e097347de49119be0f17c18b4a2d98da9919fa5884479d/json
cdccdf50922d90e847e097347de49119be0f17c18b4a2d98da9919fa5884479d/layer.tar
fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e.json
manifest.json
repositories

1、manifest.json 清单(manifest)文件是一段元数据,描述了该镜像中的内容。

[
    {
        "Config":"fce289e99eb9bca977dae136fbe2a82b6b7d4c372474c9235adc1741675f587e.json",
        "RepoTags":[
            "hello-world:latest"
        ],
        "Layers":[
            "cdccdf50922d90e847e097347de49119be0f17c18b4a2d98da9919fa5884479d/layer.tar"
        ]
    }
]

2、image-id.json文件
还有个前几位和 image-id 相同的 image-idxxx.json 文件,详细描述了镜像的信息。

3、此外有好多子目录,每个子目录是文件系统的一层(layer),每个子目录里也有一个 json 描述当前层的元数据信息。

container 容器

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的 类 和 实例 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器是一个运行的镜像实例,可以创建、启动、终止、删除它。
一个容器可以连接多个网络和存储。删除容器后,任何未在存储中进行的修改都会消失。
可以这样理解,docker image 是 docker container 的静态视角,docker container 是 docker image 的运行方式。

image和container的区别?

docker文件系统

UnionFS联合文件系统

Union File System, 简称UnionFS,是一种为Linux,FreeBSD,和NetBSD操作系统设计的把其他文件系统联合挂载到一个挂载点的文件系统服务。它通过使用branch把不同文件系统的文件和目录覆盖,形成一个一致的文件系统。这些branch是只读或者只写的。当对文件进行写操作时候,才会真正的复制
文件进行写操作。实际上本身没有对原来的文件进行修改,可以看做是共享了原来的文件。在写的时候进行修改用到了一种资源管理技术成为写时复制。

联合文件系统(Union File System):2004年由纽约州立大学石溪分校开发,它可以把多个目录(也叫分支)内容联合挂载到同一个目录下,而目录的物理位置是分开的。UnionFS允许只读和可读写目录并存,就是说可同时删除和增加内容。UnionFS应用的地方很多,比如在多个磁盘分区上合并不同文件系统的主目录,或把几张CD光盘合并成一个统一的光盘目录(归档)。另外,具有写时复制(copy-on-write)功能UnionFS可以把只读和可读写文件系统合并在一起,虚拟上允许只读文件系统的修改可以保存到可写文件系统当中。

Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。

使用mount创建一个AUFS文件系统

Linux AUFS 文件系统
https://www.cnblogs.com/sparkdev/p/11237347.html

AUFS

AUFS 的英文全称为 Advanced Mult-Layered Unification Filesystem,之前叫 Another Mult-Layered Unification Filesystem

AUFS(AnotherUnionFS)就是一种 Union FS。 AUFS 支持为每一个成员目录(类似 Git 的分支)设定只读(readonly)、读写(readwrite)和写出(whiteout-able)权限, 同时 AUFS 里有一个类似分层的概念, 对只读权限的分支可以逻辑上进行增量地修改(不影响只读部分的)。

OverlayFS

OverlayFS 是和 AUFS 相似的联合文件系统(union filesystem),它有如下特点:
设计简洁;
内核 3.18 开始已经并入内核主线
可能更快

OverlayFS 将单个 Linux 主机上的两个目录进行分层,然后将它们表示为一个目录。这些目录就叫做 layers,这个过程就被叫做 union mount。OverlayFS 将靠下一层的目录叫做 lowerdir,将靠上一层的叫做 upperdir。然后经过处理后我们看到的那个目录叫做 merged。

docker支持的UFS

Linux 各发行版实现的 UnionFS 各不相同,所以 Docker 在不同 linux 发行版中使用的 UFS 也不同。
Docker 支持几种不同的 UFS 实现,包括 AUFS、Overlay、devicemapper、BTRFS 和 ZFS。哪一个被用看系统需要并且可以通过运行 docker info 来检查,在“Storage Driver”下列出:

$ docker info |grep "Storage"
 Storage Driver: overlay2

初代 docker 默认的存储驱动是 AUFS,后来 docker 默认的存储驱动已经演进到了 overlay2

每个容器都有它们自己的文件系统视图,Docker 获取镜像所有的层,并使它们层叠在一起,以呈现为文件系统的一个视图。这个技术称为 Union Mounting,Docker 支持 Linux 上的几个 Union Mount 文件系统,主要是 OverlayFS 和 AUFS。

Docker的文件系统是如何工作的?
Docker镜像是由多个文件系统(只读层)叠加而成。当我们启动一个容器的时候,Docker会加载只读镜像层并在其上(译者注:镜像栈顶部)添加一个读写层。如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏。当删除Docker容器,并通过该镜像重新启动时,之前的更改将会丢失。在Docker中,只读层及在顶部的读写层的组合被称为Union File System(联合文件系统)。

Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。

为了能够保存(持久化)数据以及共享容器间的数据,Docker提出了Volume的概念。简单来说,Volume就是目录或者文件,它可以绕过默认的联合文件系统,而以正常的文件或者目录的形式存在于宿主机上。

一个容器中修改了基础镜像是否会影响其他容器?

如果多个容器共享同一个基础镜像,基础镜像被修改,例如 /etc/ 修改,是否会影响?
答案是不会
容器其实是在镜像的最上面加了一层读写层,在运行容器里文件改动时, 会先从镜像里要写的文件复制到容器自己的文件系统中(读写层)。 如果容器删除了,最上面的读写层也就删除了,改动也就丢失了。所以无论多少个容器共享一个镜像,所做的写操作都是从镜像的文件系统中复制过来操作的,并不会修改镜像的源文件,这种方式提高磁盘利用率。 若想持久化这些改动,可以通过 docker commit 将容器保存成新的镜像


Docker容器文件系统

volume 卷

由于docker 的镜像是由一系列的只读层组合而来,当启动一个容器时,docker 加载镜像的所有只读层,并在最上层加入一个读写层。
这样的设计固然让docker 可以提高镜像构建、储存、分发的效率,节省时间和存储空间,但也导致了一些问题:

  • 容器中的文件存在形式复杂,宿主机访问容器的文件很困难。
  • 多个容器之间的数据无法共享。
  • 容器一旦被删除,容器产生的数据将丢失。
  • 数据卷就是为了解决以上问题出现的。volume 是存在于一个或多个容器中的特定文件或文件夹,这个目录以独立于联合文件系统的形式在宿主机中存在,并为数据的共享与持久化提供便利。

volume 在容器创建时就会初始化,所以运行时就可以使用。
在不同容器间共享和重用。
对volume 中数据的操作会马上生效。
对volume 中的数据操作不会影响到镜像本身。
volume 的生存周期独立于容器的生存周期,即使删除容器,volume依然存在,没有任何容器使用它,也不会被docker 删除。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。

数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。

docker Daemon与client

Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。
Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是通过这组 API 与 Docker 引擎交互,从而完成各种功能。
因此,虽然表面上我们好像是在本机执行各种 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。
也因为这种 C/S 设计,让我们操作远程服务器的 Docker 引擎变得轻而易举。

docker daemon 接受 docker api 的请求,并管理docker 中定义的对象:镜像、容器、网络、数据卷等。
docker client 是大多数人与docker 打交道的工具,比如当你输入docker run命令时,docker client 将命令发到 daemon执行。


docker 网络

Network Overview
https://docs.docker.com/network/

安装 Docker 时,它会自动创建 3 个网络。可以使用 docker network ls 命令列出这些网络。
这 3 个网络包含在 Docker 实现中。运行一个容器时,可以使用 --net 标志指定您希望在哪个网络上运行该容器。您仍然可以使用这 3 个网络。

bridge 网络表示所有 Docker 安装中都存在的 docker0 网络。除非使用 docker run --net=<NETWORK> 选项另行指定,否则 Docker 守护进程默认情况下会将容器连接到此网络。在主机上使用 ifconfig命令,可以看到此网桥是主机的网络堆栈的一部分。
none 网络在一个特定于容器的网络堆栈上添加了一个容器。该容器缺少网络接口。
host 网络在主机网络堆栈上添加一个容器。您可以发现,容器中的网络配置与主机相同。

bridge 即桥接网络,以桥接模式连接到宿主机,即宿主机和容器之间通过 docker0 虚拟网卡连到同一个局域网。bridge是默认的网络模式
host 宿主网络,即与宿主机共用网络,这种模式相当于没有网络隔离,好处是和宿主机处于同一网络,可随意访问,都不用-p做端口映射了。
none 则表示无网络,容器将无法联网

overlay 跨主机网络。
在早期的docker版本中,是不支持跨主机通信网络驱动的,也就是说明如果容器部署在不同的节点上面,只能通过暴露端口到宿主机上,再通过宿主机之间进行通信。
docker 1.9 之后,随着docker swarm集群的推广,docker也有了自家的跨主机通信网络驱动,名叫overlay,overlay网络模型是swarm集群容器间通信的载体,将服务加入到同一个网段上的Overlay网络上,服务与服务之间就能够通信。


bridge

Use bridge networks
https://docs.docker.com/network/bridge/

安装 Docker 的时候,会在宿主机安装一个虚拟网卡 docker0, 它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。

$ ifconfig
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:88ff:fe56:8251  prefixlen 64  scopeid 0x20<link>
        ether 02:42:88:56:82:51  txqueuelen 0  (Ethernet)
        RX packets 915966  bytes 2933746454 (2.7 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 875092  bytes 62233810 (59.3 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信。例如Linux下一般是 172.17.0.1 , 并不固定。
它还给出了 MTU(接口允许接收的最大传输单元),通常是 1500 Bytes,或宿主主机网络路由上支持的默认值。
这些值都可以在服务启动的时候进行配置。


host

Use host networking
https://docs.docker.com/network/host/

host 网络和主机之间没有隔离,和主机共享一个 network namespace.

注意:使用 host 网络模式时,端口映射不起作用。 docker run 时的 -p, --publish, -P, --publish-all 参数会被忽略,并产生一条警告:
WARNING: Published ports are discarded when using host network mode

Mac和Windows上没有host网络

host 类型网络只能在 Linux 上使用, 在 Mac 和 Windows 上不支持。
The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac, Docker Desktop for Windows, or Docker EE for Windows Server.


Mac中主机和容器网络互通解决方案

1、容器内访问宿主机
使用 docker.for.mac.host.internal 或者 host.docker.internal 这两个特定域名可访问宿主机,这是 docker 容器内配置好的可解析到宿主机 IP 的 DNS, 可以直接当宿主机的 IP 使用。
进入任意容器后,ping 这两个域名都会解析到宿主机 IP

2、宿主机访问容器
通过 -p 端口映射。
例如 docker run -p 8081:80 -d nginx
之后在主机上通过 localhost:8081 可直接访问容器内的 80 端口

Networking features in Docker Desktop for Mac
https://docs.docker.com/docker-for-mac/networking/


docker-proxy

docker-proxy 是 dockerd 的子进程,用于端口映射。
docker-proxy 主要是用来做端口映射的。当我们使用 docker run 命令启动容器时,如果使用了 -p 参数,docker-proxy 组件就会把容器内相应的端口映射到主机上来,底层是依赖于 iptables 实现的。

https://learn.lianglianglee.com/专栏/由浅入深吃透%20Docker-完/11%20%20组件组成:剖析%20Docker%20组件作用及其底层工作原理.md

根据 docker-proxy 端口占用找对应容器

1、netstat -anp 查看端口占用看到是 docker-proxy 进程,获取 pid
2、ps -ef|grep pid 查看进程命令,看到是监听宿主机 8080 端口转发到 192.168.42.2 容器的 8080 端口

root     75958 11931  0 Aug21 ?        00:00:00 /usr/local/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8080 -container-ip 192.168.42.2 -container-port 8080

3、docker ps |grep 8080 端口,能看到是哪个容器在使用 8080 端口
4、docker inspect container-id |grep IPAddress 能看到使用 8080 端口的容器的 ip 就是 192.168.42.2


Docker 原理

chroot 隔离文件系统
namespace 隔离进程访问
cgroups 隔离资源使用

docker 并不是彻底的虚拟化,不同容器之间会共享内核。
Linux 虚拟机是完全的虚拟化,内核隔离。

chroot

Linux Namespace

DOCKER基础技术:LINUX NAMESPACE(上)
https://coolshell.cn/articles/17010.html

Linux CGroup

Linux CGroup 全称 Linux Control Group, 是 Linux 内核的一个功能,用来限制,控制与分离一个进程组群的资源(如CPU、内存、磁盘输入输出等)
CGroup 主要提供了如下功能:
Resource limitation: 限制资源使用,比如内存使用上限以及文件系统的缓存限制。
Prioritization: 优先级控制,比如:CPU利用和磁盘IO吞吐。
Accounting: 一些审计或一些统计,主要目的是为了计费。
Control: 挂起进程,恢复执行进程。

容器 request/limit 利用 cgroup cpu 公平调度器实现。

DOCKER基础技术:LINUX CGROUP
https://coolshell.cn/articles/17049.html


RunC

RunC 简介
https://www.cnblogs.com/sparkdev/p/9032209.html

RunC 是一个轻量级的容器运行工具,用来运行按照 OCI(Open Container Initiative) 标准格式打包的容器。
RunC 是用 golang 创建的项目。
RunC 是标准化容器运行时的一个实现,是 docker 内置的默认容器运行时。


containerd

https://containerd.io/docs/

Containerd 简介
https://www.cnblogs.com/sparkdev/p/9063042.html

containerd 是一个工业级标准的容器运行时,可以在宿主机中管理完整的容器生命周期:容器镜像的传输和存储、容器的执行和管理、存储和网络等。
containerd 是从 docker 中分离出来的,或者说 containerd 本身是 docker 的一部分。

containerd 并不是直接面向最终用户的,而是主要用于集成到更上层的系统里,比如Swarm, Kubernetes, Mesos等容器编排系统。
containerd 以 Daemon 的形式运行在系统上,通过 unix domain socket 暴露很底层的 gRPC API,上层系统可以通过这些 API 管理机器上的容器。每个 containerd 只负责一台机器,Pull 镜像,对容器的操作(启动、停止等),网络,存储都是由 containerd 完成。具体运行容器由 runC 负责,实际上只要是符合 OCI 规范的容器都可以支持。


安装Docker

Docker 只能安装在 64 位机器上。

Docker 从 1.13 版本之后采用时间线的方式作为版本号,分为社区版 CE 和企业版 EE。
社区版是免费提供给个人开发者和小型团体使用的,企业版会提供额外的收费服务,比如经过官方测试认证过的基础设施、容器、插件等。
Docker 分为 CE 和 EE 两大版本。CE 即社区版(免费,支持周期 7 个月),EE 即企业版,强调安全,付费使用,支持周期 24 个月。
Docker CE 分为 stable test 和 nightly 三个更新频道。每六个月发布一个 stable 版本 (18.09, 19.03, 19.09…)。

CentOS 安装 Docker CE

Get Docker Engine - Community for CentOS
https://docs.docker.com/install/linux/docker-ce/centos/

CentOS 安装 Docker CE
https://yeasy.gitbooks.io/docker_practice/install/centos.html

Docker 入门教程
http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html

前提条件:
1 安装 Docker Engine - Community 需要 CentOS 7 版本及以上,老版本不支持。
2 需要开启 centos-extras yum repo,默认是开启的。

安装 docker repo

1 安装 yum 仓库管理工具 yum-utils, 以及 device-mapper-persistent-data, lvm2

$ sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2

2 安装 docker repo

$ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

安装完可以在 /etc/yum.repos.d/ 中看到 docker-ce.repo 文件
docker-ce.repo 中默认只有 docker-ce-stable 是开启的,即只开启稳定版的repo

yum 安装最新版 Docker

sudo yum install docker-ce docker-ce-cli containerd.io
我安装的版本信息

已安装:
containerd.io.x86_64 0:1.2.10-3.2.el7
docker-ce.x86_64 3:19.03.5-3.el7
docker-ce-cli.x86_64 1:19.03.5-3.el7

作为依赖被安装:
container-selinux.noarch 2:2.107-3.el7

启动 Docker 并验证

启动docker
sudo systemctl start docker
设置开机启动
sudo systemctl enable docker
验证
sudo docker run hello-world
注意必须加sudo,启动ducker需要root权限
此命令将从 dokcerhub 下载一个 hello-world 镜像并启动一个容器

$ sudo docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:4fe721ccc2e8dc7362278a29dc660d833570ec2682f4e4194f4ee23e415e1064
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

将当前用户加入docker用户组

默认情况下,docker 命令会使用 Unix socket 与 Docker 引擎通讯。
而只有 root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。
出于安全考虑,一般 Linux 系统上不会直接使用 root 用户。
因此,更好地做法是将需要使用 docker 的用户加入 docker 用户组。
1 sudo groupadd docker 建立 docker 组
2 sudo usermod -aG docker $USER 将当前用户加入 docker 组
3 docker run hello-world 退出 linux 重新登录,不加 sudo 执行 docker,成功说明当前用户加入docker用户组没问题。

重启 Docker

sudo systemctl restart docker
容器会全部停止,之后需要重启容器


Docker升级到指定版本

停止 docker 服务 systemctl stop docker

查看当前版本 rpm -qa | grep docker

# rpm -qa | grep docker
nvidia-container-runtime-2.0.0-1.docker17.06.2.x86_64
docker-ce-17.06.2.ce-1.el7.centos.x86_64
nvidia-docker2-2.0.3-1.docker17.06.2.ce.noarch

删除当前版本
yum remove docker-ce-17.06.2.ce-1.el7.centos.x86_64


Mac 安装 Docker CE

Install Docker Desktop on Mac
https://docs.docker.com/docker-for-mac/install/

macOS 安装 Docker Desktop CE
https://yeasy.gitbooks.io/docker_practice/install/mac.html

Docker Desktop for Mac 要求系统最低为 2010 年以后的 Mac 机型,准确说是带 Intel MMU 虚拟化的, macOS 10.13 及以上版本, 最低 4GB 内存。
如果不满足已上要求,可以使用 Docker Toolbox 安装 Docker 来代替 Docker Desktop, Docker Toolbox 使用 Oracle VirtualBox 虚拟机而不是 HyperKit 虚拟机。

Homebrew 安装 Docker Desktop for Mac

Homebrew 的 Cask 已经支持 Docker Desktop for Mac, 因此可以很方便的使用 Homebrew Cask 来进行安装:
brew install --cask docker

查看 docker 版本 docker --version

$ docker --version
Docker version 19.03.5, build 633a0ea

启动一个 nginx 验证
docker run -d -p 80:80 --name nginx --rm nginx

docker run -d -p 80:80 --name nginx --rm nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
000eee12ec04: Pull complete
eb22865337de: Pull complete
bee5d581ef8b: Pull complete
Digest: sha256:50cf965a6e08ec5784009d0fccb380fc479826b6e0e65684d9879170a9df8566
Status: Downloaded newer image for nginx:latest
76986f1e2fffac5174f5cfa23a0988f891762bfc73904aad142d1c96c9dff75e

会自动从 docker hub 上下载最新版 nginx 镜像并启动
服务运行后,可以访问 http://localhost,如果看到了 “Welcome to nginx!”,就说明 Docker Desktop for Mac 安装成功了。


Dockerfile

Dockerfile 是一个文本文件,其内包含了一条条的 指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

Dockerfile reference
https://docs.docker.com/engine/reference/builder/

Docker 最佳实践

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

FROM 指定基础镜像

定制镜像必须以一个镜像为基础,在其上进行定制。
FROM 指定 基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。
Docker 还存在一个特殊的镜像,名为 scratch 。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

VOLUME 创建挂载点

VOLUME 在镜像中创建挂载点
VOLUME 后的目录格式可以是纯文本的目录,空格分割的多个目录,或者json格式的字符串数组

VOLUME /var/log
VOLUME /var/log /var/db
VOLUME ["/data1","/data2"]

相比于 docker run 时 -v 指定目录映射, VOLUME 指令可实现通过此镜像创建的容器内都有一个预先创建好的目录,当然使用 docker run -v 也完全可以代替 VOLUME 指令。

COPY 复制文件

COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
COPY 指令将从构建上下文目录中 源路径 的文件/目录复制到新的一层的镜像内的 目标路径 位置。
源路径 可以是多个,甚至可以是通配符
目标路径 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。

ADD 更高级的复制文件(自动解压)

ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。
比如 源路径 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 目标路径 去。
下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,
另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。
所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。
因此,这个功能其实并不实用,而且不推荐使用。

相比 COPY, ADD 其实就增加了2个特性:
1、ADD 指令可以让你使用 URL 作为 src 参数。当遇到 URL 时候,可以通过 URL 下载文件并且复制到 dest
2、ADD 的另外一个特性是有能力自动解压文件。如果 src 参数是一个可识别的压缩格式(tar, gzip, bzip2, etc)的本地文件(所以实现不了同时下载并解压),就会被解压到指定容器文件系统的路径 dest

WORKDIR 指定工作目录

WORKDIR <工作目录路径>
使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录

EXPOSE 声明端口

EXPOSE <端口1> [<端口2>...]
EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务
EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。


RUN 执行命令

RUN 指令是用来执行命令行命令的。
Dockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。

例如

RUN apk add --no-cache tzdata \
 && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
 && echo "Asia/shanghai" > /etc/timezone \
 && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache ## 清除缓存
RUN chmod +x /root/apps/start_jar.sh

第一个安装时区的多条命令放在一个 RUN 中,保证构建在同一层镜像

CMD 容器启动命令

CMD 指令用于指定默认的容器主进程的启动命令的,此命令会在容器启动且 docker run 没有指定其他命令时运行,如果 docker run 后面指定其他命令则 CMD 会被忽略
在运行时可以指定新的命令来替代镜像设置中的这个默认命令
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效

例如
CMD echo "Hello world" 运行容器 docker run -it [image] 将输出 Hello world,但当后面加上一个命令,比如 docker run -it [image] /bin/bash,CMD 会被忽略掉,命令 bash 将被执行,会进入容器。

ENTRYPOINT 入口点

ENTRYPOINT 用于设置容器启动时要执行的命令及其参数,当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为: <ENTRYPOINT> "<CMD>"
与 CMD 不同的是,不管 docker run … 后是否运行有其他命令,ENTRYPOINT 指令后的命令一定会被执行。
Dockerfile 中可以有多个 ENTRYPOINT 指令,也是只有最后一个生效

RUN/CMD/ENTRYPOINT 区别

RUN 命令会创建新的镜像层,通常用于安装应用和软件包。Dockerfile 中常常包含多个 RUN 指令,每条 RUN 指令都会生成新的一层。
CMD 和 ENTRYPOINT 都可用于设置容器的启动命令,CMD 会被 docker run 命令覆盖,而 ENTRYPOINT 不会,docker run 命令中的参数都会成为 ENTRYPOINT 的参数。

例如

ENTRYPOINT ["/usr/bin/rethinkdb"]
CMD ["--help"]

这个 dockerfile 里 ENTRYPOINT 后面还有个 CMD 的考虑是,如果 docker run 没有参数,CMD(–help)将成为 ENTRYPOINT 的默认参数,输出帮助信息,例如 docker run rethinkdb 时就会输出帮助信息。
当 docker run 有参数时会代替 CMD 命令,例如 docker run -it rethinkdb bash 可以进入容器。

Shell 格式与 Exec 格式

RUN/CMD/ENTRYPOINT 命令的格式有两种:
1、shell 格式
RUN/CMD/ENTRYPOINT <命令> 就像直接在命令行中输入的命令一样。
例如

RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
RUN yum install -y vim
CMD echo "hello zhurs"
ENTRYPOINT echo "hello zhurs"

2、exec 格式
RUN/CMD/ENTRYPOINT ["可执行文件", "参数1", "参数2"]
这更像是函数调用中的格式
CMD 和 ENTRYPOINT 推荐使用 Exec 格式,因为指令可读性更强,更容易理解。RUN 则两种格式都可以
例如

RUN ["yum", "install", "-y", "vim"]
CMD ["bin/echo", "zhurs"]
ENTRYPOINT ["/bin/echo", "hello, $wd"]
ENTRYPOINT ["java", "-jar", "/blog-server.jar"]

USER 指定容器用户

USER <user>[:<group>]

USER <UID>[:<GID>]

指定运行容器时的用户名或 UID,后续的 RUN 等指令也会使用指定的用户身份
使用 USER 指定用户时,可以使用用户名、UID 或 GID,或是两者的组合
使用 USER 指定用户后,Dockerfile 中后续的命令 RUN、CMD、ENTRYPOINT 都将使用该用户

也可以使用 docker run -u 指定用户


镜像压缩技巧

一、dockerfile 优化
1、yum安装依赖之后执行yum -y clean all && rm -rf /var/cache 可以删除无用的缓存文件
2、RUN 合并,用 && 连接多个命令,减少镜像层浪费
3、wget下载等固定的命令执行放在前面,install等容易变动的命令放在后面,可以更好的利用分层缓存机制,可以减少镜像构建的等待时间
4、从基础镜像中拷贝编译环境中文件,减少镜像层层数

FROM iregistry.baidu-int.com/baidu-base/golang:1.19-alpine3.16 AS builder
COPY --from=builder /app/bin/ /app/bin/

二、docker export/import 镜像导出再导入可压缩镜像


Docker API

https://docs.docker.com/engine/api/sdk/examples/

containers/json 查看容器列表

curl --unix-socket /var/run/docker.sock http://localhost/v1.43/containers/json
[{
  "Id":"ae63e8b89a26f01f6b4b2c9a7817c31a1b6196acf560f66586fbc8809ffcd772",
  "Names":["/tender_wing"],
  "Image":"bfirsh/reticulate-splines",
  ...
}]

Docker 常用命令

Command-line reference
https://docs.docker.com/engine/reference/commandline/cli/

docker info 查看docker信息

# docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Server Version: 18.06.1-ce
Storage Driver: overlay2
 Backing Filesystem: extfs
 Supports d_type: true
 Native Overlay Diff: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
 Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: inactive
Runtimes: nvidia runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 468a545b9edcd5932818eb9de8e72413e616e86e
runc version: 69663f0bd4b60df09991c08812a60108003fa340
init version: fec3683
Security Options:
 seccomp
  Profile: default
Kernel Version: 3.10.0-514.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 24
Total Memory: 251.6GiB
Name: myapp
ID: UF76:IPSA:VE7G:YD67:PQRT:WU4E:3EKX:2J7F:36PQ:BSOT:N2CV:XMFX
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
 127.0.0.0/8
Live Restore Enabled: false

查看 Root Dir

$ docker info |grep "Docker Root Dir"
 Docker Root Dir: /var/lib/docker

查看 docker 的 cgroup driver

$ docker info |grep Cgroup
 Cgroup Driver: cgroupfs

docker system df 查看docker磁盘占用

https://docs.docker.com/engine/reference/commandline/system_df/

docker system df 查看 docker 磁盘占用汇总信息

docker system df
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          433       42        117.4GB   97.11GB (82%)
Containers      103       89        2.145GB   1.464GB (68%)
Local Volumes   9         6         116B      58B (50%)
Build Cache     0         0         0B        0B

RECLAIMABLE 是空闲镜像(没有任何容器使用的镜像)的大小,空闲镜像可以用命令 docker image prune -a 清理

查看docker容器占用磁盘空间大小

docker system df 加 -v 参数显示详细的磁盘占用信息,能看到每个镜像、容器、卷的磁盘占用情况:

$ docker system df -v
Images space usage:

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE                SHARED SIZE         UNIQUE SIZE         CONTAINERS
blog                latest              3fc20337e23a        3 hours ago         975MB               0B                  975MB               1
elasticsearch       8.6.1-ik            b1e6c57fe659        5 months ago        1.297GB             0B                  1.297GB             1
gogs/gogs           latest              ead46421307d        3 years ago         92.38MB             0B                  92.38MB             1
nginx               alpine              377c0837328f        3 years ago         19.72MB             0B                  19.72MB             1

Containers space usage:

CONTAINER ID        IMAGE                    COMMAND                  LOCAL VOLUMES       SIZE                CREATED             STATUS              NAMES
a7faa3438b30        blog                     "java -jar /blog-ser…"   1                   0B                  3 hours ago         Up 3 hours          blog
3bd1f9b14c43        nginx:alpine             "nginx -g 'daemon of…"   0                   2B                  7 weeks ago         Up 7 weeks          nginx
7ee16ad38348        elasticsearch:8.6.1-ik   "/bin/tini -- /usr/l…"   0                   98.8MB              5 months ago        Up 5 months         es
f33daa8d3e09        gogs/gogs                "/app/gogs/docker/st…"   1                   5.57kB              5 months ago        Up 5 months         gogs

Local Volumes space usage:

VOLUME NAME                                                        LINKS               SIZE
947b26afba6c51e5d72f83f523a93fe3be7b7b83f32edc62175a8e858585ee12   0                   32.77kB
9f10a334ac3293f0e1ebcb880e1947c55017d0d878ba8d40b3925b98e494c9a1   0                   32.77kB
58f94b049406ebb796379a43061c9e81f33a309d295beb1e27c50f14a5fec417   1                   0B
716a30cd87333e7ee12d0e91cb55ad4a4005db78348c0696cfbd7568246ffab6   1                   33.6kB
08d2a5847d3b9e96b595e6efa29fdbe66cb6148eb285066069e17397463e0950   0                   32.77kB
317fdf41419fa5ac3ad7330a2c105452065e5d0846920e4ca32e9952790301dd   0                   32.77kB
8817b7c05b024d5c03d78f8ce8e5c133ca79d5e79570583135b9d892f7442785   0                   0B

Build cache usage: 0B

CACHE ID            CACHE TYPE          SIZE                CREATED             LAST USED           USAGE               SHARED

docker/containers 目录磁盘占用大清理

1、找到 docker 根目录
docker info |grep “Docker Root Dir”
默认是 /var/lib/docker

2、进入 containers 子目录,里面每个容器对应一个目录,目录名是容器ID,docker ps 结果的第一列就是容器ID的前缀
每个容器目录中有 容器ID-json.log 的日志文件,通常会比较大,可以直接 >contain-id-json.log 清理掉

限制 docker 控制台日志大小

新建或修改 docker 配置 /etc/docker/daemon.json

{
    "max-concurrent-downloads": 10,
    "log-driver": "json-file",
    "log-level": "warn",
    "log-opts": {
      "max-size": "50m",
      "max-file": "5"
    },
    "data-root": "/data/lib/docker"
}

单个日志文件最大 50m,最多 5 个日志文件,日志自动滚动:

34M    e5e0fbde1e9b3ef24bef5228e2381936568f97d7da5b7214f3883f40bc32c809-json.log
48M    e5e0fbde1e9b3ef24bef5228e2381936568f97d7da5b7214f3883f40bc32c809-json.log.1
48M    e5e0fbde1e9b3ef24bef5228e2381936568f97d7da5b7214f3883f40bc32c809-json.log.2
48M    e5e0fbde1e9b3ef24bef5228e2381936568f97d7da5b7214f3883f40bc32c809-json.log.3
48M    e5e0fbde1e9b3ef24bef5228e2381936568f97d7da5b7214f3883f40bc32c809-json.log.4

重启docker:

systemctl daemon-reload
systemctl restart docker

docker system prune 系统清理

https://docs.docker.com/engine/reference/commandline/system_prune/

docker system prune 会清理

  • 已停止的容器
  • 悬空镜像
  • 没被使用的网络
  • docker build 缓存

docker image prune -a 清理悬空镜像

https://docs.docker.com/engine/reference/commandline/image_prune/

docker image prune -a 命令可批量清理悬空镜像

docker images 看到的 none 镜像是悬空镜像(dangling images),即无标签、且不被容器使用的镜像。

REPOSITORY                                           TAG             IMAGE ID       CREATED          SIZE
<none>                                               <none>          5eaed6e23874   10 minutes ago   741MB

docker rmi 批量删除none镜像

docker rmi $(docker images | grep "none" | awk '{print $3}')
或者
docker images |grep none | awk '{print $3}' | xargs docker rmi


搜索 DockerHub 上的镜像

docker search zookeeper
NAME                               DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
zookeeper                          Apache ZooKeeper is an open-source server wh…   1075      [OK]
jplock/zookeeper                   Builds a docker image for Zookeeper version …   165                  [OK]
wurstmeister/zookeeper                                                             145                  [OK]

docker network

docker network ls 列出容器网络

docker network create

创建网络时,引擎默认为网络创建一个不重叠的子网。 该子网不是现有网络的细分。 它纯粹用于IP寻址目的。可以覆盖此默认值,并使用–subnet选项直接指定子网络值。 在桥接网络上,只能创建单个子网。
创建网络 br0
docker network create --driver=bridge --subnet=192.168.0.0/16 br0


docker run 运行容器

docker run
https://docs.docker.com/engine/reference/commandline/run/

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
以镜像 IMAGE 启动一个容器, 如果 IMAGE 不存在,会从默认仓库 Docker Hub 上下载此镜像,如果不指定 tag 默认是 latest,启动容器后执行命令 COMMAND

例1
docker run -it --rm ubuntu:18.04 bash
直接运行 ubuntu 镜像并进入启动的 Ubuntu 容器
ubuntu:18.04:这是指用 ubuntu:18.04 镜像为基础来启动容器。
bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash

例2
docker run -it adoptopenjdk/openjdk8:centos bash
会直接自动下载 centos+jdk 镜像,run 镜像,并进入容器内

参数
-d, --detach 让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下
-e, --env list 启动容器时增加环境变量
-p, --publish <宿主端口>:<容器端口> 映射宿主端口和容器端口,-p 标记可以多次使用来绑定多个端口。例如 -p 80:8080 映射本机 80 端口到容器内的 8080 端口
-P, --publish-all 随机映射宿主机上一个 49000~49900 之间的端口到容器内部的网络端口
--name string 指定启动的容器的名字
--restart string 容器退出后的重启策略,默认是 no, --restart=always 可保持容器一直运行
--rm 容器退出后随之将其删除。默认情况下,为了启动失败时查错,退出的容器并不会立即删除,除非手动 docker rm

-it 进入容器

-i, --interactive 则让容器的标准输入保持打开,交互式操作
-t, --tty 让 Docker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
-it 这两个参数一起使用,可进入容器执行命令。

--network,--net 指定容器网络模式

--network network, --net network 指定容器的网络模式,默认为 bridge
bridge 即桥接网络,以桥接模式连接到宿主机,即宿主机和容器之间通过 docker0 虚拟网卡连到同一个局域网,bridge是默认的网络模式
host 宿主网络,即与宿主机共用网络,这种模式相当于没有网络隔离,好处是和宿主机处于同一网络,可随意访问,都不用 -p 做端口映射了。
none 则表示无网络,容器将无法联网
container:c_name 连接到容器 c_name 的网络
network_name 连接到预先创建好的网络 network_name

-v 绑定文件/文件夹

-v, --volume [HOST-DIR:]CONTAINER-DIR[:OPTIONS] 在本地(宿主机)和容器间映射目录或文件。
注意此选项中 HOST-DIR 可以省略,但 CONTAINER-DIR 不能省略。如果只有一个目录,会被当做是 CONTAINER-DIR
HOST-DIR 省略时,会自动在本机创建和容器中同名的目录。
映射文件时文件名可以不相同。
如果 HOST-DIR 和 CONTAINER-DIR 都存在,host 上的文件/文件夹内容会覆盖 container 中的文件/文件夹内容。
当映射文件时,宿主机文件必须存在,因为如果不存在会自动在宿主机创建,但此时创建的就是目录了。
CONTAINER-DIR 必须是绝对路径。
HOST-DIR 可以是绝对路径,或者 volume 卷名。HOST-DIR 是绝对路径是,挂载此路径到容器。HOST-DIR 是卷名时,挂载此卷(没有会自动创建 volume)到容器。

挂载目录 -v A:B

  • 如果容器中目录 B 不存在,会先在 contanier 中创建目录 B,再将本地目录 A 中的所有文件 copy 到 B 中
  • 如果本地目录 A 不存在,可自动创建本地目录 A
  • 若果 A 和 B 都存在,会用本地目录 A 中的内容覆盖容器目录 B 中的内容

-v 可以出现多次,挂载多个不同的目录。

容器销毁后,启动容器时创建的宿主机目录不会销毁,容器运行过程中对目录的操作也都会保留在宿主机。

Docker volume 挂载时文件或文件夹不存在
https://segmentfault.com/a/1190000015684472

Use volumes
https://docs.docker.com/storage/volumes/

--mount挂载卷

--mount 挂载本地卷、目录、文件到容器中。由多个键值对组成,由逗号分隔,每一个由 key=value 元祖组成。键值对没有顺序。
type,可以是 bind,volume,tmpfs。
source,主机上的文件或目录的路径。可能用 src,source 指定。
destination,容器中的文件或目录的路径。可能用 destination,dst,target 指定。
使用 -v 和 –volume 绑定主机不存在的文件或目录,将会自动创建。始终创建的是一个目录。
使用 –mount 绑定主机上不存在的文件或目录,则不会自动创建,会产生一个错误。

-u 指定容器的用户和组

-u, --user 指定容器用户,格式 <name|uid>[:<group|gid>]
使用 docker run 启动容器应用的时候,默认使用的是root用户,并且这个 root 用户和宿主机中的 root 是同一个用户,所以容器应用进程具有很高的权限

例如
docker run -u 1000:1000 -it ubuntu bash
docker run -u centos -it centos:7 bash

--privileged特权模式

大约在 0.6 版,privileged 被引入 docker。
使用该参数,container 内的 root 拥有真正的 root 权限。
否则,container 内的 root 只是外部的一个普通用户权限。

–platform 指定架构

M1 Mac 上运行 amd64/x86_64 架构镜像,可以指定 --platform linux/amd64 参数

docker run -d --rm \
--platform linux/amd64 \
-p 8001:8001 \
--name blog \
-v /var/log/spring:/var/log/spring \
-e "SPRING_PROFILES_ACTIVE=local" \
blog

如果镜像本身是 amd64/x86_64 架构的,不需要指定,但 docker run 会报警告:
WARNING: The requested image’s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested

容器无法启动问题排查

1、docker ps -a 能显示启动失败的容器,然后 docker logs -f 看启动日志有无报错
2、docker inspect 看容器信息

3、如果 docker ps -a 也看不到容器,可以去掉 -d 后台运行参数,前台运行,而是 -it 把容器日志打印到前台,看有什么报错。


docker push 推镜像

https://docs.docker.com/engine/reference/commandline/push/
推镜像到 registry
docker push image[:tag]


docker image inspect 查看镜像详细信息

https://docs.docker.com/engine/reference/commandline/image_inspect/

docker image inspect 查看镜像的架构

其中 Architecture 是镜像的架构

docker image inspect adoptopenjdk/openjdk8:centos
[
    {
        "Id": "sha256:5c4f7308100d908ec26e8264a76d76b55591b405a76b581271dbc435c30cfbf2",
        "RepoTags": [
            "adoptopenjdk/openjdk8:centos"
        ],
        "RepoDigests": [
            "adoptopenjdk/openjdk8@sha256:56e00eb7fb3fb82dacb647590c0657534df2eb77605b83e4e706a38151eb082f"
        ],
        "Parent": "",
        "Comment": "buildkit.dockerfile.v0",
        "Created": "2023-07-01T15:09:31.842619662Z",
        "Container": "",
        "ContainerConfig": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": null,
            "Cmd": null,
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": null
        },
        "DockerVersion": "",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "LANG=en_US.UTF-8",
                "LANGUAGE=en_US:en",
                "LC_ALL=en_US.UTF-8",
                "JAVA_VERSION=jdk8u372-b07",
                "JAVA_HOME=/opt/java/openjdk"
            ],
            "Cmd": [
                "/bin/bash"
            ],
            "Image": "",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {
                "org.label-schema.build-date": "20201113",
                "org.label-schema.license": "GPLv2",
                "org.label-schema.name": "CentOS Base Image",
                "org.label-schema.schema-version": "1.0",
                "org.label-schema.vendor": "CentOS",
                "org.opencontainers.image.created": "2020-11-13 00:00:00+00:00",
                "org.opencontainers.image.licenses": "GPL-2.0-only",
                "org.opencontainers.image.title": "CentOS Base Image",
                "org.opencontainers.image.vendor": "CentOS"
            }
        },
        "Architecture": "arm64",
        "Variant": "v8",
        "Os": "linux",
        "Size": 1268913926,
        "VirtualSize": 1268913926,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/f4af2886d00a730fa35c6777754ce4ee11e1481ff22bf052f65933e70643f138/diff:/var/lib/docker/overlay2/5756458d0749a7f71398bf8162286f5cafefe39d499e04b2125662243c86ee56/diff",
                "MergedDir": "/var/lib/docker/overlay2/4bccc3a2faf71c2af258c3c7e80743331c5d72e69dc9d27c7a631406526a959c/merged",
                "UpperDir": "/var/lib/docker/overlay2/4bccc3a2faf71c2af258c3c7e80743331c5d72e69dc9d27c7a631406526a959c/diff",
                "WorkDir": "/var/lib/docker/overlay2/4bccc3a2faf71c2af258c3c7e80743331c5d72e69dc9d27c7a631406526a959c/work"
            },
            "Name": "overlay2"
        },
        "RootFS": {
            "Type": "layers",
            "Layers": [
                "sha256:65f23ff12f4df9625427d229db82655bdadd805108d3431520673d79198419ff",
                "sha256:010044c2a811335b6122c7a8d972c3e5a4f2a1f68a8253e0af581e3709179706",
                "sha256:0ede7ac16aa01ae57ed91a33faffd02eb95b81692791460dc234c68e60588489"
            ]
        },
        "Metadata": {
            "LastTagTime": "0001-01-01T00:00:00Z"
        }
    }
]

docker manifest inspect 查看镜像的manifest列表

有的镜像支持多架构,比如 https://hub.docker.com/r/adoptopenjdk/openjdk8/tags?page=1&name=centos
可以通过 docker manifest inspect 查看镜像支持哪些架构

当用户获取一个镜像时,Docker 引擎会首先查找该镜像是否有 manifest 列表,如果有的话 Docker 引擎会按照 Docker 运行环境(系统及架构)查找出对应镜像(例如 golang:alpine)。如果没有的话会直接获取镜像(例如上例中我们构建的 username/test)。

docker manifest inspect adoptopenjdk/openjdk8:centos
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 956,
         "digest": "sha256:099e5eda268dc834873028c94eb7031d2acb2dad0ce92edccee78d6fd7666b7b",
         "platform": {
            "architecture": "arm64",
            "os": "linux",
            "variant": "v8"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 953,
         "digest": "sha256:c185c64ed5993198248bf8aad74e706077b34ee30f7d0260e19c55397ac0f2f2",
         "platform": {
            "architecture": "arm",
            "os": "linux",
            "variant": "v7"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 954,
         "digest": "sha256:65147bda6f81bfba8922d4c3ae896184551ac5d8fe750e3ba9916846b9eadb91",
         "platform": {
            "architecture": "ppc64le",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 954,
         "digest": "sha256:d2c6b89782cc256d9cef8b4dd72623f7594eb935ba62ba762ddfc17aaace8bbb",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      }
   ]
}

docker pull 下载镜像

https://docs.docker.com/engine/reference/commandline/pull/

docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
具体的选项可以通过 docker pull –help 命令看到
Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。
仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。

例如 docker pull ubuntu:18.04
上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。
而镜像名称是 ubuntu:18.04,因此将会获取官方镜像 library/ubuntu 仓库中标签为 18.04 的镜像。

–platform 指定架构

有的镜像同一 tag 提供多架构的,可通过 –platform 参数指定架构

docker pull adoptopenjdk/openjdk8:centos –platform amd64
docker pull centos:7 –platform linux/amd64

修改docker镜像存储位置

一般默认情况下 docker 镜像的存放位置为 /var/lib/docker(可通过 docker info 命令查看 Docker Root Dir 配置项), 而此目录一般不会分配很大空间,docker 上跑的东西太多就会发现此目录满了。

注意:Mac 虽然 docker info 看到的 Docker Root Dir 也是 /var/lib/docker, 但其实镜像并不在这里,而是在
$HOME/Library/Containers/com.docker.docker/Data/vms/0/data/Docker.raw 这个单独的文件中,并且无法修改。

解决方法:

软链接

systemctl stop docker       # 关闭 docker 服务
mv /var/lib/docker /var/lib/docker.bak   # 备份当前 docker 镜像文件目录
ln -s /data/docker  /var/lib/docker         # 设置软连接,其中 /data/docker 目录为新的存放 docker 镜像目录
cp -rp /var/lib/docker.bak /data/docker        # 将旧的 docker 文件拷贝过去

修改 docker 配置文件

指定镜像和容器存放路径的参数是 --graph=/var/lib/docker,我们只需要修改配置文件指定启动参数即可。
1、修改 /etc/sysconfig/docker 配置文件
添加

OPTIONS=--graph="/root/data/docker" --selinux-enabled -H fd://

2、如果 docker 是 1.12 或以上的版本,可以修改(或新建)/etc/docker/daemon.json 文件。修改后会立即生效,不需重启 docker 服务。
改为

{
  "graph": "/data/docker",
  "default-shm-size": "64g"
}

修改docker.service服务配置

修改 docker 安装后自动创建的 linux 服务脚本 /etc/systemd/system/docker.service
改为

ExecStart=/usr/bin/dockerd -g /data/docker/ -H fd://

然后 systemctl daemon-reload 重载 unit 配置文件

docker no space left on device

docker 镜像所在磁盘空间不足时 pull image 会报这个错误。

四个修改Docker默认存储位置的方法
https://blog.51cto.com/forangela/1949947


docker images 列出本机镜像

docker image ls 列出镜像,等价于 docker images

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        11 months ago       1.84kB

列表包含了 仓库名、标签、镜像 ID、创建时间 以及 所占用的空间

docker image ls ubuntu 根据仓库名列出镜像
docker image ls ubuntu:18.04 指定仓库名和标签列出镜像
docker image ls -q 只列出镜像ID,经常用做其他命令的输入参数

docker image rm 删除本地镜像

docker image rm [选项] <镜像1> [<镜像2> ...] 等价于 docker rmi

docker image rm centos 用镜像名,也就是 仓库名:标签,来删除镜像
docker image rm $(docker image ls -q redis) 删除所有仓库名为 redis 的镜像
docker image rm $(docker image ls -q -f before=mongo:3.2) 删除所有在 mongo:3.2 之前的镜像


docker build 构建镜像

docker build
https://docs.docker.com/engine/reference/commandline/build/

docker build [选项] <上下文路径/URL/->
-t, --tag 指定 image 文件的名字,后面还可以用冒号指定标签,例如 name:tag。如果不指定,默认的标签就是 latest
-f, --file 指定 Dockerfile 文件名称,默认是 PATH/Dockerfile

docker build -t myapp . 在当前目录使用 Dockerfile 构建 myapp 镜像
docker build -t myapp -f path-to-Dockerfile . 在当前目录使用 path-to-Dockerfile 构建 myapp 镜像


–platform 构建多架构镜像

构建 amd64 架构的镜像

docker build -f devops/blog.Dockerfile --platform=amd64 -t blog .

M1 Mac 上打的镜像默认是arm64架构的

在 M1 Mac 上使用 Docker 构建的镜像架构默认是 arm64(不指定 –platform 参数时)。M1芯片是基于ARM架构的处理器,因此Docker会根据宿主机的架构来构建相应的镜像。
这意味着,在M1 Mac上构建的镜像只能在支持arm64架构的设备上运行,而不能在传统的x86架构设备上运行。

如果 Dockerfile 中指定了特定架构的基础镜像,例如 FROM ubuntu:amd64,则构建的镜像继承了基础镜像的架构,因此也是 amd64 架构的


构建上下文

docker 构建命令 docker build [选项] <上下文路径/URL/-> 最后需要制定上下文目录
当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

如果在 Dockerfile 中这么写:
COPY ./package.json /app/
这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json, 而是复制 上下文(context) 目录下的 package.json。

一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore ,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。

实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,而且并不要求必须位于上下文目录中,比如可以用 -f ../Dockerfile.php 参数指定某个文件作为 Dockerfile。

.dockerignore

.dockerignore 用于剔除不需要作为上下文传递给 Docker 引擎的文件


docker history 查看镜像历史

https://docs.docker.com/engine/reference/commandline/history/
查看镜像历史

--no-trunc 不截断输出结果,不加这个选项的话 CREATED BY 中显示不全

docker history zookeeper:3.4.14
IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
4b03fe5b3f64   2 months ago   /bin/sh -c #(nop)  CMD ["zkServer.sh" "start…   0B
<missing>      2 months ago   /bin/sh -c #(nop)  ENTRYPOINT ["/docker-entr…   0B
<missing>      2 months ago   /bin/sh -c #(nop) COPY file:9258d11b841666f1…   1.16kB
<missing>      2 months ago   /bin/sh -c #(nop)  ENV PATH=/usr/local/openj…   0B
<missing>      2 months ago   /bin/sh -c #(nop)  EXPOSE 2181 2888 3888        0B
<missing>      2 months ago   /bin/sh -c #(nop)  VOLUME [/data /datalog /l…   0B
<missing>      2 months ago   /bin/sh -c #(nop) WORKDIR /zookeeper-3.4.14     0B
<missing>      2 months ago   |2 DISTRO_NAME=zookeeper-3.4.14 GPG_KEY=3F7A…   60MB
<missing>      2 months ago   /bin/sh -c #(nop)  ARG DISTRO_NAME=zookeeper…   0B
<missing>      2 months ago   /bin/sh -c #(nop)  ARG GPG_KEY=3F7A1D16FA421…   0B
<missing>      2 months ago   /bin/sh -c set -eux;     apt-get update;    …   12.3MB
<missing>      2 months ago   /bin/sh -c set -eux;     groupadd -r zookeep…   329kB
<missing>      2 months ago   /bin/sh -c #(nop)  ENV ZOO_CONF_DIR=/conf ZO…   0B
<missing>      2 months ago   /bin/sh -c set -eux;   arch="$(dpkg --print-…   109MB
<missing>      2 months ago   /bin/sh -c #(nop)  ENV JAVA_VERSION=8u292       0B
<missing>      2 months ago   /bin/sh -c #(nop)  ENV LANG=C.UTF-8             0B
<missing>      2 months ago   /bin/sh -c #(nop)  ENV PATH=/usr/local/openj…   0B
<missing>      2 months ago   /bin/sh -c { echo '#/bin/sh'; echo 'echo "$J…   27B
<missing>      2 months ago   /bin/sh -c #(nop)  ENV JAVA_HOME=/usr/local/…   0B
<missing>      2 months ago   /bin/sh -c set -eux;  apt-get update;  apt-g…   8.82MB
<missing>      2 months ago   /bin/sh -c #(nop)  CMD ["bash"]                 0B
<missing>      2 months ago   /bin/sh -c #(nop) ADD file:7362e0e50f30ff454…   69.3MB

docker ps 列出本机容器

docker container ls 等于 docker ps
docker ps 列出本机正在运行的容器
docker ps -a 列出本机所有容器,包括终止运行的容器

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
5edb883d9423        nginx:alpine        "nginx -g 'daemon of…"   10 months ago       Up 10 months                            nginx
e057ebc04ff5        gogs/gogs           "/app/gogs/docker/st…"   12 months ago       Up 12 months                            gogs
756930ef3388        search              "java -jar /app.jar"     12 months ago       Up 12 months                            search
992185baf34b        disqus              "java -jar /app.jar"     12 months ago       Up 12 months                            disqus
d89f76910cd6        statistic           "java -jar /app.jar"     12 months ago       Up 12 months                            statistic
2dcbb37eb434        eureka              "java -jar /app.jar"     12 months ago       Up 12 months                            eureka

docker container start 启动已终止容器

可以利用 docker container start 命令,直接将一个已经终止的容器启动运行。

docker stop 终止容器

docker container stop 等于 docker stop
docker stop [OPTIONS] CONTAINER [CONTAINER...]

docker rm 删除容器

docker rm 等于 docker container rm
docker rm [OPTIONS] CONTAINER [CONTAINER...]
如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。


docker exec 进入容器执行命令

docker exec 等于 docker container exec
docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
-it 这两个参数一起使用,可进入容器执行命令。
常用格式
docker exec -it 容器ID bash
docker exec -it 容器ID sh

先查看当前运行的容器

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                NAMES
724d51cc7cf7        nginx               "nginx -g 'daemon of…"   47 minutes ago      Up 47 minutes       0.0.0.0:80->80/tcp   mystifying_knuth

指定容器id进入容器

$ docker exec -it 724d51cc7cf7 bash
root@724d51cc7cf7:/#

OCI runtime exec failed bash executable file not found

docker exec -it 33e828194205 bash
OCI runtime exec failed: exec failed: container_linux.go:346: starting container process caused “exec: "bash": executable file not found in $PATH”: unknown

原因: 原因是该容器并没有 bash
解决: 改用 sh
docker exec -it 33e828194205 sh


docker commit 将容器保存为镜像

当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

-a, --author string 指定修改的作者
-m, --message string 记录本次修改的内容

例如

$ docker commit \
    -a "Tao Wang <twang2218@gmail.com>" \
    -m "修改了默认网页" \
    webserver \
    nginx:v2

其中 -a 指定修改的作者,而 -m 则是记录本次修改的内容,这点和 git 版本控制相似,不过这里这些信息可以省略留空。


docker save 导出镜像

https://docs.docker.com/engine/reference/commandline/save/

保存镜像到 tar 文件,默认输出到 stdout, 默认包含所有的 layer, 所有的tags, 当然也可以指定tags
-o, --output docker save 默认是输出到 stdout, 通过 -o 参数可指定保存的档案文件名

docker save busybox > busybox.tar 保存镜像 busybox 的所有版本到 busybox.tar 文件
docker save -o myimage.tar myimage:tag1 保存镜像 myimage 的 tag1 版本到 myimage.tar 文件

利用 docker save/load 导入导出镜像

方法一、利用 gzip/gunzip 压缩解压
导出镜像并压缩为gzip
docker save myimage:latest | gzip > myimage_latest.tar.gz 导出镜像并压缩

倒入镜像压缩包
gunzip -c myimage_tag.tar.gz | docker load

方法二、或者使用 tar 命令压缩和解压

docker save myimage:tag > myap.tar
tar -zcvf myap.tar.gz myap.tar

解压导入镜像

tar -xzvf myap.tar.gz
docker load < myap.tar

当然,如果 tar 文件本身不大,不用压缩也可以。

内网下载docker镜像

1、在可以联网的机子上执行 docker pull 命令下载镜像,如: sudo docker pull freewil/bitcoin-testnet-box,命令使用参考 Docker pull 命令
2、然后运行 docker save 命令将镜像保存为 tar 归档文件,如:docker save -o bitcoin-testnet-box.tar freewil/bitcoin-testnet-box,命令使用参考Docker save 命令
3、将保存的 bitcoin-testnet-box.tar 归档文件拷贝进内网机器
4、内网机器上执行 dock load 命令加载保存的 tar 归档文件,如:docker load -i bitcoin-testnet-box.tar

docker load 导入镜像

https://docs.docker.com/engine/reference/commandline/load/

从 tar 文件或标准输入 stdin 导入镜像,会重建镜像和所有的 tag.
这里的 tar 文件是用 docker save 命令导出的镜像。
--input , -i : 指定导入的文件,代替 STDIN。
--quiet , -q : 精简输出信息。

docker load < busybox.tar.gz 利用输入重定向从 tar 文件导入镜像
docker load --input fedora.tar 通过 --input 参数指定 tar 文件导入镜像


docker cp 在容器和宿主机间拷贝文件/目录

https://docs.docker.com/engine/reference/commandline/cp/

docker cp 等于 docker container cp
docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH

示例
1、将容器中的文件 /root/123.mp4 拷贝到宿主机的 /root/mp4/ 目录中,其中 335d957237c9 是容器id

docker cp 335d957237c9:/root/123.mp4 /root/mp4/

2、将容器中的 /root/images 目录拷贝到宿主机的 /root/data/ 目录中,其中 335d957237c9 是容器id

docker cp 335d957237c9:/root/images /root/data/

3、将宿主机当前目录中的 xx.mp4 文件拷贝到容器的 /root/data/ 目录中,其中 video-processor 是容器名

docker cp xx.mp4 video-processor:/root/data/

docker tag 重命名镜像

https://docs.docker.com/engine/reference/commandline/tag/
docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]
重命名镜像,将 source 重命名为 target
docker tag myapp:ver1 myapp:ver2


docker logs 查看容器日志

docker logs [OPTIONS] CONTAINER
如果容器无法启动,先 docker ps -a 找到启动失败的容器id, 然后 docker logs 容器id 查看失败日志
注意为了保留启动失败的容器,不能加 --rm 否则失败后容器文件就被删除了。
docker logs -f 容器id 以滚动方式查看容器日志


docker inspect 显示容器信息

docker inspect [OPTIONS] NAME|ID [NAME|ID...]

$ docker inspect blog
[
    {
        "Id": "1b2a948873a0b44a6553dfee4ab96160195d1d41c10047d08744f9331f370195",
        "Created": "2023-08-24T13:24:10.537047009Z",
        "Path": "java",
        "Args": [
            "-jar",
            "/blog-server.jar"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 30211,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2023-08-24T13:24:10.735225254Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },
        "Image": "sha256:8a4f3b90771a63fca73de426a882b9e540cdb3e82bb54f5c6828fdd0fcac7019",
        "ResolvConfPath": "/var/lib/docker/containers/1b2a948873a0b44a6553dfee4ab96160195d1d41c10047d08744f9331f370195/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/1b2a948873a0b44a6553dfee4ab96160195d1d41c10047d08744f9331f370195/hostname",
        "HostsPath": "/var/lib/docker/containers/1b2a948873a0b44a6553dfee4ab96160195d1d41c10047d08744f9331f370195/hosts",
        "LogPath": "/var/lib/docker/containers/1b2a948873a0b44a6553dfee4ab96160195d1d41c10047d08744f9331f370195/1b2a948873a0b44a6553dfee4ab96160195d1d41c10047d08744f9331f370195-json.log",
        "Name": "/blog",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": [
                "/home/centos/git/hexo/source/_posts:/home/centos/git/hexo/source/_posts",
                "/var/log/spring:/var/log/spring"
            ],
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "host",
            "PortBindings": {},
            "RestartPolicy": {
                "Name": "no",
                "MaximumRetryCount": 0
            },
            "AutoRemove": true,
            "VolumeDriver": "",
            "VolumesFrom": null,
            "CapAdd": null,
            "CapDrop": null,
            "Capabilities": null,
            "Dns": [],
            "DnsOptions": [],
            "DnsSearch": [],
            "ExtraHosts": null,
            "GroupAdd": null,
            "IpcMode": "private",
            "Cgroup": "",
            "Links": null,
            "OomScoreAdj": 0,
            "PidMode": "",
            "Privileged": false,
            "PublishAllPorts": false,
            "ReadonlyRootfs": false,
            "SecurityOpt": null,
            "UTSMode": "",
            "UsernsMode": "",
            "ShmSize": 67108864,
            "Runtime": "runc",
            "ConsoleSize": [
                0,
                0
            ],
            "Isolation": "",
            "CpuShares": 0,
            "Memory": 0,
            "NanoCpus": 0,
            "CgroupParent": "",
            "BlkioWeight": 0,
            "BlkioWeightDevice": [],
            "BlkioDeviceReadBps": null,
            "BlkioDeviceWriteBps": null,
            "BlkioDeviceReadIOps": null,
            "BlkioDeviceWriteIOps": null,
            "CpuPeriod": 0,
            "CpuQuota": 0,
            "CpuRealtimePeriod": 0,
            "CpuRealtimeRuntime": 0,
            "CpusetCpus": "",
            "CpusetMems": "",
            "Devices": [],
            "DeviceCgroupRules": null,
            "DeviceRequests": null,
            "KernelMemory": 0,
            "KernelMemoryTCP": 0,
            "MemoryReservation": 0,
            "MemorySwap": 0,
            "MemorySwappiness": null,
            "OomKillDisable": false,
            "PidsLimit": null,
            "Ulimits": null,
            "CpuCount": 0,
            "CpuPercent": 0,
            "IOMaximumIOps": 0,
            "IOMaximumBandwidth": 0,
            "MaskedPaths": [
                "/proc/asound",
                "/proc/acpi",
                "/proc/kcore",
                "/proc/keys",
                "/proc/latency_stats",
                "/proc/timer_list",
                "/proc/timer_stats",
                "/proc/sched_debug",
                "/proc/scsi",
                "/sys/firmware"
            ],
            "ReadonlyPaths": [
                "/proc/bus",
                "/proc/fs",
                "/proc/irq",
                "/proc/sys",
                "/proc/sysrq-trigger"
            ]
        },
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/1348860d518c757e7e1ff3c1e6f28c07c19de62d89a7cef4c80ceaad16fe462b-init/diff:/var/lib/docker/overlay2/e8b6a48ed759efa3ae67ecb40263598c0a8b6ee5301462bcc3d83e5e793b1cd2/diff:/var/lib/docker/overlay2/1fa0bf9fa238529796268727234d64c5ae43c0a772777ab5e994e507e0d8941b/diff:/var/lib/docker/overlay2/fe8966ed84314a86ccefe3ba6f82e63e1cf8d26228ac5617e19de5458665a80a/diff:/var/lib/docker/overlay2/02e5f96ab2f2cec701cb9112b46bb70e5f0c9e74e5e2a724209fe877a927ce75/diff:/var/lib/docker/overlay2/1961851b14190d3e33ff7d4da98ebbc6ef15e766381c4d6f33b07b8bfcd640ec/diff:/var/lib/docker/overlay2/71c38e268e4e12d19c1eb6a5f9dac9d368f13e27e38e8cc65fad6d44e0134677/diff:/var/lib/docker/overlay2/a1151919d07928f90f2dfec6cd51f8d481058a9d89a4a85119f6a507ee606f8a/diff:/var/lib/docker/overlay2/bf2ff9aa57906946d9b64e98f7868a098384e9b1eb6a7847dd0b39553be7350e/diff:/var/lib/docker/overlay2/184ccf4335849b0b9d4427c47fed2d4e098fa278841cb3b73b8296a053eb7da2/diff:/var/lib/docker/overlay2/309a7215c73eed1e4362551ef038f89b22fa388289b6bae168eef3d0849e54c8/diff:/var/lib/docker/overlay2/72c8c882fb6eef99e0d0e65c6346123f7e6d518edb5f8a87251dcd1e3d150994/diff",
                "MergedDir": "/var/lib/docker/overlay2/1348860d518c757e7e1ff3c1e6f28c07c19de62d89a7cef4c80ceaad16fe462b/merged",
                "UpperDir": "/var/lib/docker/overlay2/1348860d518c757e7e1ff3c1e6f28c07c19de62d89a7cef4c80ceaad16fe462b/diff",
                "WorkDir": "/var/lib/docker/overlay2/1348860d518c757e7e1ff3c1e6f28c07c19de62d89a7cef4c80ceaad16fe462b/work"
            },
            "Name": "overlay2"
        },
        "Mounts": [
            {
                "Type": "bind",
                "Source": "/var/log/spring",
                "Destination": "/var/log/spring",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },
            {
                "Type": "volume",
                "Name": "30fc28fdb0150ac58196c22ed7a93859f4f006a6144e45a7ac1debf48d325dc9",
                "Source": "/var/lib/docker/volumes/30fc28fdb0150ac58196c22ed7a93859f4f006a6144e45a7ac1debf48d325dc9/_data",
                "Destination": "/tmp",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },
            {
                "Type": "bind",
                "Source": "/home/centos/git/hexo/source/_posts",
                "Destination": "/home/centos/git/hexo/source/_posts",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
        "Config": {
            "Hostname": "lightsail",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "SPRING_PROFILES_ACTIVE=prod",
                "PATH=/opt/java/openjdk/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "LANG=en_US.UTF-8",
                "LANGUAGE=en_US:en",
                "LC_ALL=en_US.UTF-8",
                "JAVA_VERSION=jdk8u382-b05",
                "JAVA_HOME=/opt/java/openjdk",
                "LD_LIBRARY_PATH=:/usr/lib64"
            ],
            "Cmd": null,
            "Image": "blog",
            "Volumes": {
                "/tmp": {}
            },
            "WorkingDir": "",
            "Entrypoint": [
                "java",
                "-jar",
                "/blog-server.jar"
            ],
            "OnBuild": null,
            "Labels": {
                "org.label-schema.build-date": "20201113",
                "org.label-schema.license": "GPLv2",
                "org.label-schema.name": "CentOS Base Image",
                "org.label-schema.schema-version": "1.0",
                "org.label-schema.vendor": "CentOS",
                "org.opencontainers.image.created": "2020-11-13 00:00:00+00:00",
                "org.opencontainers.image.licenses": "GPL-2.0-only",
                "org.opencontainers.image.title": "CentOS Base Image",
                "org.opencontainers.image.vendor": "CentOS"
            }
        },
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "1784dcbf33ad6f4b77cee88b29fd5a20c004ae83314449e6a8e3d65ec4da0432",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {},
            "SandboxKey": "/var/run/docker/netns/default",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "",
            "Gateway": "",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "",
            "IPPrefixLen": 0,
            "IPv6Gateway": "",
            "MacAddress": "",
            "Networks": {
                "host": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "f2bdcc4e19f4fddcc771e5107d9aeb36e7bdfcd1a2ff93afb3d34da6cbce33b7",
                    "EndpointID": "f27b48b87af78897921f13db8187b4c17756f8d63a0ff51ac292e0372c6ce443",
                    "Gateway": "",
                    "IPAddress": "",
                    "IPPrefixLen": 0,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "",
                    "DriverOpts": null
                }
            }
        }
    }
]

如何查看容器的启动命令

1、如果在容器内部,可以用 ps -fe 查看,其中 1 号进程就是启动命令
2、在容器外部,物理机上,可以用 docker inspect container_id/name 查看,Config 中的 Cmd 或 Entrypoint 就是启动命令


docker-compose

compose 用于定义和运行由多个 docker 镜像组成的 service,可通过配置文件配置 service 由哪些镜像组成,然后通过一个命令一次性将这些 docker 镜像启动。

docker / compose
https://github.com/docker/compose

Compose安装
https://containerization-automation.readthedocs.io/zh_CN/latest/docker/compose/Compose%E5%AE%89%E8%A3%85/

安装 docker-compose

1、下载二进制可执行文件,命名为 docker-compose

sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

2、sudo chmod +x /usr/local/bin/docker-compose

3、查看版本,有输出就是安装成功

$ docker-compose -v
docker-compose version 1.26.2, build eefe0d31

Install Docker Compose
https://docs.docker.com/compose/install/

docker-compose.yml配置文件

compose 文件包含了 4 个顶级键:
version:指定文件规范版本
services:指定要操作的容器
networks:指定共用的网络配置
volumes:指定共用的存储配置

可以使用 docker-compose config 验证和查看待操作的Compose文件

docker-compose.yml格式版本和docker兼容对照表

compose配置文件格式版本 Docker版本
3.8 19.03.0+
3.7 18.06.0+
3.6 18.02.0+
3.5 17.12.0+
3.4 17.09.0+
3.3 17.06.0+
3.2 17.04.0+
3.1 1.13.1+
3.0 1.13.0+
2.4 17.12.0+
2.3 17.06.0+
2.2 1.13.0+
2.1 1.12.0+
2.0 1.10.0+
1.0 1.9.1.+

Compose and Docker compatibility matrix
https://docs.docker.com/compose/compose-file/

depends_on

解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 web

version: '3'

services:
  web:
    build: .
    depends_on:
      - db
      - redis

  redis:
    image: redis

  db:
    image: postgres

注意:web 服务不会等待 redis db 「完全启动」之后才启动。


docker compose 命令

命令说明 - 《Docker — 从入门到实践》
https://yeasy.gitbook.io/docker_practice/compose/commands

指定配置文件

如果你使用默认配置文件名称,不需要显式指定 -f docker-compose.yml
如若需指定配置文件,必须在 docker-compose 后面指定,不能在 up 等子命令后面指定,否则无效;

up

构建,(重新)创建,启动,链接一个服务相关的容器。
docker compose up [options] [--scale SERVICE=NUM...] [SERVICE...]
该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。
可以说,大部分时候都可以直接通过该命令来启动一个项目。

-d, --detach 后台启动
默认情况,docker-compose up 启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。
当通过 Ctrl-C 停止命令时,所有容器将会停止。
如果使用 docker-compose up -d ,将会在后台启动并运行所有的容器。

--no-recreate 默认情况,如果服务容器已经存在,docker-compose up 将会尝试停止容器,然后重新创建(保持使用 volumes-from 挂载的卷),以保证新启动的服务匹配 docker-compose.yml 文件的最新内容。如果用户不希望容器被停止并重新创建,可以使用 docker-compose up --no-recreate 这样将只会启动处于停止状态的容器,而忽略已经运行的服务。如果需要的话,这样将会启动已经停止的容器。

指定 service
如果用户只想重新部署某个服务,可以使用 docker-compose up --no-deps -d <SERVICE_NAME> 来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务。

链接的服务都将会启动,除非他们已经运行。

start
启动一个已经存在的服务容器。

stop

停止一个已经运行的容器,但不删除它。通过 docker-compose start 可以再次启动这些容器。

ps

查看容器,在 docker-compose.yml 所在目录中执行 docker-compose ps 只查看此 compose.yml 描述的容器状态

# docker-compose ps
   Name                  Command               State                     Ports
-------------------------------------------------------------------------------------------------
zookeeper_1   /docker-entrypoint.sh zkSe ...   Up      0.0.0.0:2181->2181/tcp, 2888/tcp, 3888/tcp
zookeeper_2   /docker-entrypoint.sh zkSe ...   Up      0.0.0.0:2182->2181/tcp, 2888/tcp, 3888/tcp
zookeeper_3   /docker-entrypoint.sh zkSe ...   Up      0.0.0.0:2183->2181/tcp, 2888/tcp, 3888/tcp

当然也可以直接 docker ps 查看所有容器的状态

# docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                                        NAMES
936ca6a90d3f        zookeeper:3.4.10                "/docker-entrypoint.…"   27 minutes ago      Up 27 minutes       2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp   zookeeper_1
475f0ec7ef1b        zookeeper:3.4.10                "/docker-entrypoint.…"   27 minutes ago      Up 27 minutes       2888/tcp, 3888/tcp, 0.0.0.0:2182->2181/tcp   zookeeper_2
d8b28308b6eb        zookeeper:3.4.10                "/docker-entrypoint.…"   27 minutes ago      Up 27 minutes       2888/tcp, 3888/tcp, 0.0.0.0:2183->2181/tcp   zookeeper_3

docker compose 网络

默认情况下,Compose会为我们的应用创建一个网络,服务的每个容器都会加入该网络中。这样,容器就可被该网络中的其他容器访问,不仅如此,该容器还能以服务名称作为hostname被其他容器访问。

默认情况下,应用程序的网络名称基于Compose的工程名称,而项目名称基于docker-compose.yml所在目录的名称。如需修改工程名称,可使用–project-name标识或COMPOSE_PORJECT_NAME环境变量。

举个例子,假如一个应用程序在名为myapp的目录中,并且docker-compose.yml如下所示:

version: "3"
services:
  web:
    build: .
    ports:
      - "8000:8000"
  db:
    image: postgres
    ports:
      - "8001:5432"

当我们运行 docker-compose up时,将会执行以下几步:

创建一个名为myapp_default的网络;
使用web服务的配置创建容器,它以“web”这个名称加入网络myapp_default;
使用db服务的配置创建容器,它以“db”这个名称加入网络myapp_default。

Networking in Compose
https://docs.docker.com/compose/networking/

network_mode使用默认docker网络

等于 docker 命令的 --network 参数

network_mode: "host" # 使用主机网络
network_mode: "bridge"
network_mode: "none"

network_mode
https://docs.docker.com/compose/compose-file/#network_mode


docker仓库

docker hub

Docker Hub 是由 Docker 公司负责维护的公共注册中心,包含大量的容器镜像,Docker 工具默认从这个公共镜像库下载镜像。
https://hub.docker.com/

推送镜像到 Docker Hub

1、注册 Docker Hub 账号

2、在命令行登录 Docker Hub 账号

$ docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: masikkk
Password:
WARNING! Your password will be stored unencrypted in /home/centos/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

3、查看本地镜像

# 查看本地镜像
$ docker images
REPOSITORY                            TAG                 IMAGE ID            CREATED             SIZE
masikkk/centos7-openjdk21             20240123            5996f993958f        7 weeks ago         720MB
masikkk/centos7-openjdk21-tesseract   20240123            15a6611140d7        40 hours ago        1.07GB

打镜像 tag 时需要注意名称中必须包含 Docker Hub 账号,我的账号是 masikkk,则镜像必须是 masikkk/ 开头,否则推送到 Docker Hub 时报错:

[centos@lightsail ~]$ docker push centos7-openjdk21
The push refers to repository [docker.io/library/centos7-openjdk21]
9441bde1b4e1: Preparing
0256674c5104: Preparing
174f56854903: Preparing
denied: requested access to the resource is denied

4、推送镜像到 Docker Hub

$ docker push masikkk/centos7-openjdk21:20240123
The push refers to repository [docker.io/masikkk/centos7-openjdk21]
9441bde1b4e1: Pushed
0256674c5104: Pushed
174f56854903: Pushed
20240123: digest: sha256:4707f90e6f702881dacd53a5480c129b8aca4baef6f6d2615a8243bda965d45d size: 954

Push the image
https://docs.docker.com/get-started/04_sharing_app/#push-the-image


docker-registry 私人仓库

library/registry 是官方提供的工具,可以用于构建私有的镜像仓库。也就是这个私人仓库程序本身就是一个镜像。
使用官方的 registry 镜像来启动私有仓库。
默认情况下,仓库会被创建在容器的 /var/lib/registry 目录下。
通过 -v 将本地目录 /home/centos/docker/registry 映射到容器的 /var/lib/registry 目录,这样上传到容器的镜像就保存到本地了。

$ docker run -d \
    -p 5000:5000 \
    --restart=always \
    --name registry \
    -v /home/centos/docker/registry:/var/lib/registry \
    registry

nvidia-docker

NVIDIA / nvidia-docker
https://github.com/NVIDIA/nvidia-docker

nvidia-docker 相关组件
nvidia-docker2
nvidia-container-runtime
nvidia-container-toolkit
libnvidia-container
之间的关系

What’s the difference between the lastest nvidia-docker and nvidia container runtime? #1268
https://github.com/NVIDIA/nvidia-docker/issues/1268


实践

busybox 镜像

BusyBox 是一个集成了一百多个最常用 Linux 命令和工具(如 cat、echo、grep、mount、telnet 等)的精简工具箱,它只需要几 MB 的大小,很方便进行各种快速验证。


hexo镜像

docker build -f centos7-openjdk21-nodejs16-hexo7.Dockerfile -t masikkk/centos7-openjdk21-node16-hexo7:20240317 .

docker run -d -it --rm \
-v ~/.ssh:/home/centos/.ssh \
masikkk/centos7-openjdk21-node16-hexo7:20240317 bash

后台启动容器,然后 docker exec 进入容器

根据 https://github.com/nodesource/distributions/tree/master?tab=readme-ov-file#rpm-supported-versions CentOS7最高只支持 nodejs 17,18及之上版本需要 CentOS8

安装git
sudo yum install -y git

git clone git@github.com:masikkk/hexo.git

安装 nodejs 17

curl -fsSL https://rpm.nodesource.com/setup_16.x | sudo bash -
sudo yum install -y nodejs
# node -v
v17.9.0
# npm -v
8.5.5

Centos7-OpenJDK 镜像

nimmis/centos7-openjdk8 镜像

nimmis/java-centos
https://hub.docker.com/r/nimmis/java-centos/tags?page=1&name=openjdk-8

Dockerfile
https://github.com/nimmis/docker-java-centos/blob/master/openjdk-8-jdk/Dockerfile

只有amd64架构的

docker pull nimmis/java-centos:openjdk-8-jdk
REPOSITORY                                           TAG             IMAGE ID       CREATED          SIZE
nimmis/java-centos                                   openjdk-8-jdk   8a93794b15b0   5 years ago      402MB

AdoptOpenJDK/centos7-openjdk8 镜像

adoptopenjdk/openjdk8
https://hub.docker.com/r/adoptopenjdk/openjdk8/tags?page=1&name=centos

Dockerfile 文件
https://github.com/AdoptOpenJDK/openjdk-docker/blob/master/8/jdk/centos/Dockerfile.hotspot.releases.full

AdoptOpenJDK 已被官方废弃,最高只到 jdk16
注意此镜像有多种架构,M1 Mac 上默认会拉取 arm64 架构的版本,如果想要使用 amd64/x86_64 架构版本,需要通过 --platform amd64 参数指定

docker pull adoptopenjdk/openjdk8:centos –platform amd64
docker pull adoptopenjdk/openjdk8:centos-slim –platform amd64

REPOSITORY TAG IMAGE ID CREATED SIZE
adoptopenjdk/openjdk8 centos-slim f7719a74b210 17 hours ago 471MB
adoptopenjdk/openjdk8 centos a72f04e4db6d 17 hours ago 579MB


自己构建 Centos7-OpenJDK8 镜像

1、创建 centos7-openjdk8.Dockerfile

FROM centos:7

MAINTAINER masikkk.com

RUN yum update -y && \
yum install -y wget && \
yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel && \
yum clean all

# Set environment variables.
ENV HOME /root

# Define working directory.
WORKDIR /root

# Define default command.
CMD ["java","-version"]

2、构建镜像
进入 centos7-openjdk8.Dockerfile 文件所在目录
docker build -f centos7-openjdk8.Dockerfile -t masikkk/centos7-openjdk8:20240123 .
结果

$ docker images
REPOSITORY                            TAG                 IMAGE ID            CREATED             SIZE
masikkk/centos7-openjdk8              20240123            a44b575730c8        56 seconds ago      602MB

3、运行镜像,由于 Dockerfile 里的 CMD 命令就是 java -version,执行 docker run 可直接看到 java 版本

$ docker run masikkk/centos7-openjdk8:20240123
openjdk version "1.8.0_392"
OpenJDK Runtime Environment (build 1.8.0_392-b08)
OpenJDK 64-Bit Server VM (build 25.392-b08, mixed mode)

也可以执行 docker run -it --rm centos7-openjdk8:20240123 bash 进入镜像看看 java 版本

4、推送到 Docker Hub

$ docker push masikkk/centos7-openjdk8:20240123
The push refers to repository [docker.io/masikkk/centos7-openjdk8]
ceb11fb82205: Pushed
174f56854903: Mounted from masikkk/centos7-openjdk21-tesseract
20240123: digest: sha256:93b85206411b507e869aeb841118b9bb23db26f53d3979a70c2f957b39fd244c size: 742

镜像已发布到 Docker Hub 公开仓库,可直接使用
https://hub.docker.com/repository/docker/masikkk/centos7-openjdk8


自己构建 CentOS7-OpenJDK21 镜像

注意:下面 docker 镜像的构建是在 linux 上执行的,如果在 M1 Mac 上打镜像但最后在 Linux 上跑,需要注意 M1 Mac 上默认会拉下来 arm64 架构的镜像,而 Linux 上需要 amd64 架构的镜像。

1、创建 centos7-openjdk21.Dockerfile

# 自己基于CentOS7构建带JDK8的镜像
FROM centos:7

MAINTAINER masikkk.com

# 更新yum,安装wget
RUN yum update -y && \
    yum install -y wget && \
    yum clean all

# https://jdk.java.net/21/
RUN mkdir -p /opt/java && \
    cd /opt/java && \
    wget https://download.java.net/java/GA/jdk21.0.1/415e3f918a1f4062a0074a2794853d0d/12/GPL/openjdk-21.0.1_linux-x64_bin.tar.gz && \
    tar -xf openjdk-21.0.1_linux-x64_bin.tar.gz && \
    rm -rf openjdk-21.0.1_linux-x64_bin.tar.gz

# Set environment variables.
ENV JAVA_HOME /opt/java/jdk-21.0.1
ENV PATH $PATH:$JAVA_HOME/bin

# Define default command.
CMD ["java","-version"]

2、进入 centos7-openjdk21.Dockerfile 所在目录,执行:

docker build -f centos7-openjdk21.Dockerfile  -t masikkk/centos7-openjdk21:20240123 .

结果:

$ docker images
REPOSITORY                            TAG                 IMAGE ID            CREATED
masikkk/centos7-openjdk21             20240123            5996f993958f        7 weeks ago         720MB

3、执行 docker run masikkk/centos7-openjdk21:20240123 验证,由于 Dockerfile 里的 CMD 命令就是 java -versiondocker run 可直接看到 java 版本

$ docker run masikkk/centos7-openjdk21:20240123
openjdk version "21.0.1" 2023-10-17
OpenJDK Runtime Environment (build 21.0.1+12-29)
OpenJDK 64-Bit Server VM (build 21.0.1+12-29, mixed mode, sharing)

也可以 docker run -it --rm masikkk/centos7-openjdk21:20240123 bash 进入容器验证
或者 docker run -d -it --rm masikkk/centos7-openjdk21:20240123 bash 后台启动容器,然后 docker exec 进入容器。

4、推送到 Docker Hub

$ docker push masikkk/centos7-openjdk21:20240123
The push refers to repository [docker.io/masikkk/centos7-openjdk21]
9441bde1b4e1: Pushed
0256674c5104: Pushed
174f56854903: Pushed
20240123: digest: sha256:4707f90e6f702881dacd53a5480c129b8aca4baef6f6d2615a8243bda965d45d size: 954

镜像已发布到 Docker Hub 公开仓库,可直接使用
https://hub.docker.com/repository/docker/masikkk/centos7-openjdk21/general


自己构建 CentOS7-OpenJDK21-tesseract 镜像

1、创建 centos7-openjdk21-tesseract.Dockerfile

# 基于自己构建的 Centos7-OpenJDK21 镜像
FROM masikkk/centos7-openjdk21:20240123

MAINTAINER masikkk.com

# 更新yum,安装wget
RUN yum update -y && \
    yum install -y wget && \
    yum clean all


# 安装 tesseract,加 --nogpgcheck 忽略公钥检查
RUN yum-config-manager --add-repo http://download.opensuse.org/repositories/home:/Alexander_Pozdnyakov/RHEL_7/ && \
    yum update -y && \
    yum install tesseract -y --nogpgcheck

# 容器默认命令
CMD ["tesseract","-v"]

2、进入 centos7-openjdk21-tesseract.Dockerfile 所在目录,执行:

docker build -f centos7-openjdk21-tesseract.Dockerfile -t masikkk/centos7-openjdk21-tesseract:20240123 .

结果:

$ docker images
REPOSITORY                            TAG                 IMAGE ID            CREATED             SIZE
masikkk/centos7-openjdk21-tesseract   20240123            15a6611140d7        40 hours ago        1.07GB

3、执行 docker run masikkk/centos7-openjdk21-tesseract:20240123 由于 CMD 默认命令是 tesseract -v,可直接看到 tesseract 版本号:

$ docker run masikkk/centos7-openjdk21-tesseract:20240123
tesseract 4.1.3
 leptonica-1.76.0
  libjpeg 6b (libjpeg-turbo 1.2.90) : libpng 1.5.13 : libtiff 4.0.3 : zlib 1.2.7 : libwebp 0.3.0
 Found AVX2
 Found AVX
 Found FMA
 Found SSE

也可以 docker run -it --rm masikkk/centos7-openjdk21-tesseract:20240123 bash 进入容器验证。

4、推送到 Docker Hub

$ docker push masikkk/centos7-openjdk21-tesseract:20240123
The push refers to repository [docker.io/masikkk/centos7-openjdk21-tesseract]
0dcffa355b31: Pushed
ce751aba1cc4: Pushed
9441bde1b4e1: Mounted from masikkk/centos7-openjdk21
0256674c5104: Mounted from masikkk/centos7-openjdk21
174f56854903: Mounted from masikkk/centos7-openjdk21
20240123: digest: sha256:cf72104eac31f86e2a082d7c13205ef697b5bddd841c3ab27823b1078d3420ce size: 1379

镜像已发布到 Docker Hub 公开仓库,可直接使用
https://hub.docker.com/repository/docker/masikkk/centos7-openjdk21-tesseract/general


Docker 构建部署 SpringBoot 服务

Spring Boot with Docker
https://spring.io/guides/gs/spring-boot-docker/

docker hub / openjdk
https://hub.docker.com/_/openjdk

构建基于 openjdk:8-alpine 的 SpringBoot 服务镜像

1、在项目根目录下添加 Dockerfile 文件:

FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN apk add --no-cache tzdata \
 && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
 && echo "Asia/shanghai" > /etc/timezone \
 && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache ## 清除缓存
ENTRYPOINT ["java","-jar","/app.jar"]

由于 alpine 中是 UTC 时区,需要改为东八区,但 alpine 中默认又没有 /usr/share/zoneinfo/Asia/Shanghai 时区文件,只能先安装时区文件 tzdata,再链接到 /etc/localtime

2、先用 maven 把 spring boot 项目打包为可运行的 jar 包
mvn package

3、构建 SpringBoot Docker 镜像
在 Dockerfile 所在的项目根目录下 构建镜像 docker build -t blog-server .
注意一定要在 Dockerfile 所在的目录,最后那个 . 指定了当前目录是构建上下文。

$ docker build -t blog-server .
Sending build context to Docker daemon  117.6MB
Step 1/6 : FROM openjdk:8-jdk-alpine
 ---> a3562aa0b991
Step 2/6 : VOLUME /tmp
 ---> Using cache
 ---> d0ee8c49a7f7
Step 3/6 : ARG JAR_FILE=target/*.jar
 ---> Using cache
 ---> 8db784deccb1
Step 4/6 : COPY ${JAR_FILE} app.jar
 ---> 506e8cf80e06
Step 5/6 : RUN apk add --no-cache tzdata  && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime  && echo "Asia/shanghai" > /etc/timezone  && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache ## 清除缓存
 ---> Running in 74f8c63c1c21
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz
(1/1) Installing tzdata (2020c-r1)
Executing busybox-1.29.3-r10.trigger
OK: 107 MiB in 55 packages
Removing intermediate container 74f8c63c1c21
 ---> 8686579ddbe7
Step 6/6 : ENTRYPOINT ["java","-jar","/app.jar"]
 ---> Running in acacc24c7c34
Removing intermediate container acacc24c7c34
 ---> b6c2b7ac458f
Successfully built b6c2b7ac458f
Successfully tagged blog-server:latest

4、查看构建好的镜像 docker images

$ docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
blog-server               latest              b6c2b7ac458f        37 seconds ago      224MB

构建基于 centos7-openjdk8 的 SpringBoot 服务镜像

1、准备 Dockerfile 文件

# https://hub.docker.com/r/adoptopenjdk/openjdk8/tags?page=1&name=centos
# https://github.com/AdoptOpenJDK/openjdk-docker/blob/master/8/jdk/centos/Dockerfile.hotspot.releases.full
FROM adoptopenjdk/openjdk8:centos

VOLUME /tmp

MAINTAINER masikkk.com

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

ARG JAR_FILE=blog-server/target/blog-server.jar
COPY ${JAR_FILE} /blog-server.jar

ENTRYPOINT ["java","-jar","/blog-server.jar"]

2、构建镜像
我在 M1 Mac 上默认会构建 “Architecture”: “arm64” 架构的镜像,为了能在 Linux 上复用,通过 --platform=amd64 指定构建 amd64/x86_64 架构的镜像
docker build -f blog.Dockerfile –platform=amd64 -t blog .


启动 SpringBoot Docker 容器

linux 中启动 SpringBoot Docker 容器

docker run -d --rm \
--network host \
--name blog-server \
-v /var/log/spring:/var/log/spring \
-e "SPRING_PROFILES_ACTIVE=local" \
blog-server

解释下

  • 以 host 网络模式启动容器,和宿主机完全共享网络,不需要再配置 -p 端口映射
  • 我的业务日志写到了 /data/log/spring 目录中,通过 -v 映射到本地
  • -e "SPRING_PROFILES_ACTIVE=local" 指定启动spring的profile

启动后进入容器 docker exec -it blog-server sh
查看系统版本

# cat os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.9.4
PRETTY_NAME="Alpine Linux v3.9"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"

Mac 上启动 SpringBoot Docker 容器

Mac 上启动,由于 mac 中没有 host 网络,需要 -p 指定端口映射

docker run -d --rm \
-p 8001:8001 \
--name blog \
-v /var/log/spring:/var/log/spring \
-e "SPRING_PROFILES_ACTIVE=local" \
blog

M1 Mac 上运行 amd64/x86_64 架构镜像

docker run -d --rm \
--platform linux/amd64 \
-p 8001:8001 \
--name blog \
-v /var/log/spring:/var/log/spring \
-e "SPRING_PROFILES_ACTIVE=local" \
blog

Docker 容器部署 Nginx

docker hub 的 nginx 官方页面里有比较全面的用法
https://hub.docker.com/_/nginx

Nginx 容器教程
https://www.ruanyifeng.com/blog/2018/02/nginx-docker.html

拉取最新 alpine 版本 Nginx 镜像

如果不带标签的话,默认拉取的是 latest 版本镜像,也就是 1.17.9, mainline, 1, 1.17, latest,这个版本用的是 debian:buster-slim linux ,不是很习惯,好多基本命令没有。
所以加上 alpine 标签拉取 1.17.9-alpine, mainline-alpine, 1-alpine, 1.17-alpine, alpine 版本镜像

拉取官方 nginx alpine 版本镜像的最新版 docker pull nginx:alpine

$ docker pull nginx:alpine
alpine: Pulling from library/nginx
4167d3e14976: Pull complete
bb292c78f105: Pull complete
Digest: sha256:abe5ce652eb78d9c793df34453fddde12bb4d93d9fbf2c363d0992726e4d2cad
Status: Downloaded newer image for nginx:alpine
docker.io/library/nginx:alpine

启动默认 nginx:alpine 镜像

启动默认 nginx 容器
docker run -d -p 80:80 --name my-nginx --rm nginx:alpine
也可以不先拉取 nginx 镜像,启动时自动从 docker hub 拉取最新版 nginx 镜像。

进容器中查看系统版本
查看 /etc/os-release 文件

# cat /etc/os-release
NAME="Alpine Linux"
ID=alpine
VERSION_ID=3.10.4
PRETTY_NAME="Alpine Linux v3.10"
HOME_URL="https://alpinelinux.org/"
BUG_REPORT_URL="https://bugs.alpinelinux.org/"

nginx 版本

# nginx -v
nginx version: nginx/1.17.9

配置目录
/etc/nginx/nginx.conf
日志目录
/var/log/nginx/error.log
/var/log/nginx/access.log


指定配置文件启动 nginx:alpine

还使用原始的 nginx:alpine 官方镜像,只不过在启动命令中加入自己的配置,很方便

aws 上的nginx

docker run -d --rm \
--network host \
--name nginx \
-e TZ="Asia/Shanghai" \
-v /home/centos/git/hexo/nginx/nginx-centos.conf:/etc/nginx/nginx.conf:ro \
-v /home/centos/git/hexo/public:/home/centos/git/hexo/public \
-v /home/centos/git/image:/home/centos/git/image \
-v /var/log/nginx:/var/log/nginx \
nginx:alpine

解释:

  • 以 host 网络模式启动容器,和宿主机完全共享网络,不需要再配置 -p 端口映射,否则容器内的 nginx 需要配置主机 ip 才能访问主机网络。
  • 容器中默认是 UTC 时区,比我们的时间慢8小时,改为 Asia/Shanghai 即东八区
  • 文件和文件夹映射
  • 把本地的 nginx 配置文件 /home/centos/git/hexo/nginx/nginx-centos.conf 映射到容器中的 /etc/nginx/nginx.conf,会自动覆盖容器的配置文件,注意必须用绝对路径。这两个文件可以不同名。
  • 把静态文件目录 /home/centos/git/hexo/public 映射到容器中的同名目录,由于容器中没有这个目录,会自动新建并将全部内容拷贝进去,而且,以后宿主机此文件夹的更新会完全反应到容器中。
  • 把图片文件目录 /home/centos/git/hexo/image 映射到容器中的同名目录,由于容器中没有这个目录,会自动新建并将全部内容拷贝进去,而且,以后宿主机此文件夹的更新会完全反应到容器中。
  • 把容器中 nginx 的日志目录 /var/log/nginx 映射到本机的同名目录,由于本机没有 /var/log/nginx 目录,会自动创建,之后在本机即可看 nginx 日志
  • --rm 表示停止容器时删除容器文件,因为 nginx 是无状态的即开即用服务,且配置文件和静态文件映射自宿主机文件,容器内不需要任何持久化,停止后删掉就行,下次再重新启动一个新容器。
  • --name nginx 指定一个容器名,方便之后的操作

构建自己的 nginx 镜像并启动

在 nginx-centos.conf 文件所在的目录中新建 Dockerfile 文件

FROM nginx:alpine
COPY nginx-centos.conf /etc/nginx/nginx.conf
RUN echo "Asia/shanghai" > /etc/timezone \
 && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

解释:

  • 基础镜像是 alpine 版本最新 nginx
  • 把自己的配置文件 nginx-centos.conf 拷贝到容器中覆盖默认的 /etc/nginx/nginx.conf
  • 修改容器的时区

在 Dockerfile 所在目录内执行
docker build -t nginx-masikkk . 构建自己的 nginx 镜像,名为 nginx-masikkk, 以当前路径为上下文路径
一定要注意上下文路径是当前所在文件夹,所以 COPY 指令才可以直接拷贝当前文件夹中的 nginx-centos.conf 文件

$ docker build -t nginx-masikkk .
Sending build context to Docker daemon  15.87kB
Step 1/3 : FROM nginx
latest: Pulling from library/nginx
Digest: sha256:50cf965a6e08ec5784009d0fccb380fc479826b6e0e65684d9879170a9df8566
Status: Downloaded newer image for nginx:latest
 ---> 231d40e811cd
Step 2/3 : COPY nginx-centos.conf /etc/nginx/nginx.conf
 ---> e45c37eea53f
Step 3/3 : RUN echo "Asia/shanghai" > /etc/timezone  && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
 ---> Running in f8cc0a26834a
Removing intermediate container f8cc0a26834a
 ---> 20ad396f30ea
Successfully built 20ad396f30ea
Successfully tagged nginx-masikkk:latest

查看构建好的镜像

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
nginx-masikkk       latest              20ad396f30ea        4 seconds ago       126MB
nginx               latest              231d40e811cd        4 weeks ago         126MB

启动自己的 nginx 容器

docker run -d --rm \
--network host \
--name nginx \
-v /home/centos/git/hexo/public:/home/centos/git/hexo/public \
-v /home/centos/git/hexo/image:/home/centos/git/hexo/image \
-v /var/log/nginx:/var/log/nginx \
nginx-masikkk

这样启动命令可以少两个参数。


nginx.conf 中的用户名必须是 nginx

docker 版 nginx 默认以用户 nginx 启动,一开始我使用了自己当前的用户,一直无法启动
报错
2019/12/23 03:49:40 [emerg] 1#1: getpwnam(“centos”) failed in /etc/nginx/nginx.conf:2
nginx: [emerg] getpwnam(“centos”) failed in /etc/nginx/nginx.conf:2
在 nginx.conf 中改为 user nginx nginx; 后好了


docker容器部署prometheus

INSTALLATION
https://prometheus.io/docs/prometheus/latest/installation/

docker 太方便了,准备好配置文件后直接启动就行,自动从 dockerhub 拉取最新官方镜像
我的启动命令如下:

docker run -d --rm \
--network host \
--name prometheus \
-v /home/centos/git/masikkk/prometheus.yml:/etc/prometheus/prometheus.yml \
prom/prometheus

解释下:
-d 后台运行
--rm 停止容器后删掉容器文件
--network host 与宿主机完全共享网络,默认是bridge桥接,无法在nginx中通过localhost转发请求
--name grafana 指定启动的容器名,方便按名称stop等操作
-v 映射配置文件,具体说是宿主机配置文件覆盖容器中的配置文件,我的配置文件在 git 仓库中,方便保存,也可以记录修改历史。

参考笔记 Prometheus


docker容器部署grafana

Installing Grafana
https://grafana.com/docs/grafana/latest/installation/

Installing using Docker
https://grafana.com/docs/grafana/latest/installation/docker/

第一次执行时直接从 dockerhub 拉取最新版本 grafana

docker run -d --rm \
--network host \
--name grafana \
grafana/grafana

解释下:
-d 后台运行
--rm 停止容器后删掉容器文件
--network host 与宿主机完全共享网络,默认是bridge桥接,无法在nginx中通过localhost转发请求
--name grafana 指定启动的容器名,方便按名称stop等操作

参考笔记 Grafana


docker容器部署邮件服务器

tomav/docker-mailserver
https://github.com/tomav/docker-mailserver

tvial/docker-mailserver
https://hub.docker.com/r/tvial/docker-mailserver

利用Docker自建多功能加密邮件服务器
https://www.itmanbu.com/docker-mail-server.html

Mail Server Docker
https://blog.liyang.info/2018/04/17/mail-server-docker/


遇到的问题

容器内的nginx无法访问宿主机网络

我在宿主机上用 nodejs 起了个 http 服务器做 git webhooks,监听 1121 端口,在容器内的 nginx 要把访问 1121 端口的请求转发到宿主机上,是这么写的配置

# webhooks.开头的三级域名访问, git的webhooks, 转发到后台 node.js web服务
server {
    listen       80;
    server_name  webhooks.devgou.com webhooks.madaimeng.com webhooks.masikkk.com;
    location / {
            proxy_pass http://localhost:1121;
    }
}

访问 webhooks 接口的时候报错 502 Bad Gateway, nginx 日志报错

2019/12/25 10:52:31 [error] 6#6: *1892 connect() failed (111: Connection refused) while connecting to upstream, client: 220.194.45.154, server: webhooks.devgou.com, request: "GET /pull/hexo HTTP/1.1", upstream: "http://127.0.0.1:1121/pull/hexo", host: "webhooks.devgou.com"
220.194.45.154 - - [25/Dec/2019:10:52:31 +0800] "GET /pull/hexo HTTP/1.1" 502 559 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" "-"
2019/12/25 10:52:32 [error] 6#6: *1892 no live upstreams while connecting to upstream, client: 220.194.45.154, server: webhooks.devgou.com, request: "GET /favicon.ico HTTP/1.1", upstream: "http://localhost/favicon.ico", host: "webhooks.devgou.com", referrer: "http://webhooks.devgou.com/pull/hexo"

也就是找不到 localhost:1121 服务,想了想也是, 容器和宿主机是两个系统,容器的 localhost 和宿主机不同,肯定找不到这个服务。
下面就是解决如何在容器中访问宿主机的网络了

方法一,bridge网络模式的容器使用宿主机在docker0网卡上的ip访问宿主机。
安装 Docker 的时候,会在宿主机安装一个虚拟网卡 docker0, 它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。
在宿主机上 ifconfig 查看 docker0 网卡的ip地址,Linux下一般是 172.17.0.1 , macOS下一般是 192.168.65.1,并不固定。
在容器中使用宿主机ip访问的问题是,配置写死ip非常不灵活,和docker anywhere deploy 的原则相悖。

方法二,使用host宿主机网络模式启动容器
Docker容器运行的时候有 host 、 bridge 、 none 三种网络可供配置。默认是 bridge ,即桥接网络,以桥接模式连接到宿主机; host 是宿主网络,即与宿主机共用网络; none 则表示无网络,容器将无法联网。

当容器使用 host 网络时,容器与宿主共用网络,这样就能在容器中访问宿主机网络,那么容器的 localhost 就是宿主机的 localhost 。
启动容器时加参数 --network host 表示已宿主机网络模式启动容器。此时不需要-p 80:80做端口映射了,因为本身与宿主机共用了网络,容器中暴露端口等同于宿主机暴露端口。
这种模式的问题是破坏了容器之间的隔离性,好处是网络访问很方便。

Docker容器访问宿主机网络的方法
https://m.jb51.net/article/149173.htm


查看容器中的日志

容器中的 nginx 日志文件都重定向到了重启的标准输出和标准错误输出,所以直接到容器中看 access.log 是没有内容的。
有如下两个方法看日志:
1、可以在容器外通过 docker logs -f nginx-name 查看日志
/var/log/nginx/access.log -> /dev/stdout
/var/log/nginx/error.log -> /dev/stderr

2、启动容器时 -v /var/log/nginx:/var/log/nginx 将容器内日志目录映射到本机,之后直接在本机即可看日志。


alpine镜像中jvm工具不可用问题

在alpine镜像中使用诸如jstack,jinfo工具,有如下报错:
1: Unable to get pid of LinuxThreads manager thread

原因是pid为1,发现PID为1的时候,工具不可用。

解决:
1、Dockerfile里使用如下命令启动:
CMD ["/bin/sh","-c","java APP","&& 1"]
2、或者使用 --init 参数来启动docker


java.lang.UnsatisfiedLinkError: no fontmanager in java.library.path

某些需要绘制图表的 java 应用在 docker 中可能遇到这个问题,比如需要创建 excel 表格的,原因是缺少字体配置。

no fontmanager in java.library.path
https://stackoverflow.com/questions/37251309/no-fontmanager-in-java-library-path/39861372

java.lang.UnsatisfiedLinkError: no fontmanager in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at sun.font.FontManagerNativeLibrary$1.run(FontManagerNativeLibrary.java:61)
at java.security.AccessController.doPrivileged(Native Method)
at sun.font.FontManagerNativeLibrary.<clinit>(FontManagerNativeLibrary.java:32)
at sun.font.SunFontManager$1.run(SunFontManager.java:339)
at java.security.AccessController.doPrivileged(Native Method)
at sun.font.SunFontManager.<clinit>(SunFontManager.java:335)

修改容器时区

多数容器中默认是 Etc/UTC UTC 时区

可以进入容器修改时区,不推荐
docker exec -it <CONTAINER NAME> bash
echo "Asia/Shanghai" > /etc/timezone

启动容器时用环境变量TZ指定时区
docker run -e TZ="Asia/Shanghai" -d -p 80:80 --name nginx nginx

启动容器时-v绑定主机时区文件
利用 volume 可以在启动一个 container 时指定使用主机的时区文件,就可以把 container 的时区与主机同步
docker run -v /etc/localtime:/etc/localtime <IMAGE:TAG>

Dockerfile中指定时区

如果是打包自己的镜像,可以在 Dockerfile 中增加

RUN echo "Asia/shanghai" > /etc/timezone \
 && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

前提是容器的linux中本身就有 /usr/share/zoneinfo/Asia/Shanghai 时区文件

基于alpine的镜像中修改时区

alpine 中默认也是 UTC 时区,需要改为东八区,但 alpine 中默认又没有 /usr/share/zoneinfo/Asia/Shanghai 时区文件,只能先安装时区文件 tzdata,再链接到 /etc/localtime

RUN apk add --no-cache tzdata \
 && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
 && echo "Asia/shanghai" > /etc/timezone \
 && rm -rf /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache ## 清除缓存

Mac中使用docker时遇到的问题

mac中映射的文件需要先设置共享

Mac docker 中启动时报错
docker: Error response from daemon: Mounts denied:
The path /var/log/spring
is not shared from OS X and is not known to Docker.
You can configure shared paths from Docker -> Preferences… -> File Sharing.
See https://docs.docker.com/docker-for-mac/osxfs/#namespaces for more info.

因为通过 -v 映射了一个不在共享配置中的文件夹

Mac 系统中想要和容器共享文件夹的话,需要先配置
默认已共享了 /Users/, /Volumes/, /private/, and /tmp
可在 Preferences -> File sharing 中增加共享目录

mac上宿主机和容器互相网络访问

mac中没有docker0虚拟网卡
受限于 Docker Desktop for Mac 底层的网络实现, Mac上的docker没有 docker0 网卡。

从容器中访问mac网络的方法
通过调试用DNS host.docker.internal

从mac上访问容器网络的方法:
使用 -p 80:80 暴露端口, --network host 不起作用

host 类型网络只能在 Linux 上使用, 在 Mac 和 Windows 上不支持。
https://docs.docker.com/network/host/
The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac, Docker Desktop for Windows, or Docker EE for Windows Server.

https://docs.docker.com/docker-for-mac/networking/


M1 Mac 上构建和运行 amd64/x86_64 架构镜像

为了在 M1 Mac 上模拟运行 amd64/x86_64 架构的 Linux 环境,需要构建和运行 amd64/x86_64 架构镜像。

1、M1 Mac 上构建 amd64/x86_64 架构镜像
docker build -f devops/blog.Dockerfile –platform=amd64 -t blog .

2、M1 Mac 上运行 amd64/x86_64 架构镜像,可以指定 --platform linux/amd64 参数

docker run -d --rm \
--platform linux/amd64 \
-p 8001:8001 \
--name blog \
-v /var/log/spring:/var/log/spring \
-e "SPRING_PROFILES_ACTIVE=local" \
blog

如果镜像本身是 amd64/x86_64 架构的,不需要指定,但 docker run 会报警告:
WARNING: The requested image’s platform (linux/amd64) does not match the detected host platform (linux/arm64/v8) and no specific platform was requested


上一篇 Python

下一篇 Linux上搭建邮件服务器

阅读
评论
25.6k
阅读预计114分钟
创建日期 2019-08-12
修改日期 2024-01-23
类别
目录
  1. 基本概念
    1. 为什么要用docker?
    2. repository 仓库
    3. image 镜像
      1. image 镜像内容
    4. container 容器
      1. image和container的区别?
    5. docker文件系统
      1. UnionFS联合文件系统
        1. 使用mount创建一个AUFS文件系统
        2. AUFS
        3. OverlayFS
      2. docker支持的UFS
      3. 一个容器中修改了基础镜像是否会影响其他容器?
    6. volume 卷
    7. docker Daemon与client
    8. docker 网络
      1. bridge
      2. host
        1. Mac和Windows上没有host网络
      3. Mac中主机和容器网络互通解决方案
    9. docker-proxy
      1. 根据 docker-proxy 端口占用找对应容器
    10. Docker 原理
      1. chroot
      2. Linux Namespace
      3. Linux CGroup
      4. RunC
      5. containerd
  2. 安装Docker
    1. CentOS 安装 Docker CE
      1. 安装 docker repo
      2. yum 安装最新版 Docker
      3. 启动 Docker 并验证
      4. 将当前用户加入docker用户组
      5. 重启 Docker
      6. Docker升级到指定版本
    2. Mac 安装 Docker CE
      1. Homebrew 安装 Docker Desktop for Mac
  3. Dockerfile
    1. Docker 最佳实践
    2. FROM 指定基础镜像
    3. VOLUME 创建挂载点
    4. COPY 复制文件
    5. ADD 更高级的复制文件(自动解压)
    6. WORKDIR 指定工作目录
    7. EXPOSE 声明端口
    8. RUN 执行命令
    9. CMD 容器启动命令
    10. ENTRYPOINT 入口点
      1. RUN/CMD/ENTRYPOINT 区别
      2. Shell 格式与 Exec 格式
    11. USER 指定容器用户
    12. 镜像压缩技巧
  4. Docker API
    1. containers/json 查看容器列表
  5. Docker 常用命令
    1. docker info 查看docker信息
      1. 查看 Root Dir
      2. 查看 docker 的 cgroup driver
    2. docker system df 查看docker磁盘占用
      1. 查看docker容器占用磁盘空间大小
      2. docker/containers 目录磁盘占用大清理
      3. 限制 docker 控制台日志大小
    3. docker system prune 系统清理
    4. docker image prune -a 清理悬空镜像
      1. docker rmi 批量删除none镜像
    5. docker search
    6. docker network
      1. docker network create
    7. docker run 运行容器
      1. -it 进入容器
      2. --network,--net 指定容器网络模式
      3. -v 绑定文件/文件夹
      4. --mount挂载卷
      5. -u 指定容器的用户和组
      6. --privileged特权模式
      7. –platform 指定架构
      8. 容器无法启动问题排查
    8. docker push 推镜像
    9. docker image inspect 查看镜像详细信息
      1. docker image inspect 查看镜像的架构
    10. docker manifest inspect 查看镜像的manifest列表
    11. docker pull 下载镜像
      1. –platform 指定架构
      2. 修改docker镜像存储位置
        1. 软链接
        2. 修改 docker 配置文件
        3. 修改docker.service服务配置
      3. docker no space left on device
    12. docker images 列出本机镜像
    13. docker image rm 删除本地镜像
    14. docker build 构建镜像
      1. –platform 构建多架构镜像
      2. M1 Mac 上打的镜像默认是arm64架构的
      3. 构建上下文
      4. .dockerignore
    15. docker history 查看镜像历史
    16. docker ps 列出本机容器
    17. docker container start 启动已终止容器
    18. docker stop 终止容器
    19. docker rm 删除容器
    20. docker exec 进入容器执行命令
      1. OCI runtime exec failed bash executable file not found
    21. docker commit 将容器保存为镜像
    22. docker save 导出镜像
      1. 利用 docker save/load 导入导出镜像
      2. 内网下载docker镜像
    23. docker load 导入镜像
    24. docker cp 在容器和宿主机间拷贝文件/目录
    25. docker tag 重命名镜像
    26. docker logs 查看容器日志
    27. docker inspect 显示容器信息
      1. 如何查看容器的启动命令
  6. docker-compose
    1. 安装 docker-compose
    2. docker-compose.yml配置文件
      1. docker-compose.yml格式版本和docker兼容对照表
      2. depends_on
    3. docker compose 命令
      1. 指定配置文件
      2. up
      3. stop
      4. ps
    4. docker compose 网络
      1. network_mode使用默认docker网络
  7. docker仓库
    1. docker hub
      1. 推送镜像到 Docker Hub
    2. docker-registry 私人仓库
  8. nvidia-docker
  9. 实践
    1. busybox 镜像
    2. hexo镜像
    3. Centos7-OpenJDK 镜像
      1. nimmis/centos7-openjdk8 镜像
      2. AdoptOpenJDK/centos7-openjdk8 镜像
      3. 自己构建 Centos7-OpenJDK8 镜像
      4. 自己构建 CentOS7-OpenJDK21 镜像
      5. 自己构建 CentOS7-OpenJDK21-tesseract 镜像
    4. Docker 构建部署 SpringBoot 服务
      1. 构建基于 openjdk:8-alpine 的 SpringBoot 服务镜像
      2. 构建基于 centos7-openjdk8 的 SpringBoot 服务镜像
      3. 启动 SpringBoot Docker 容器
        1. linux 中启动 SpringBoot Docker 容器
        2. Mac 上启动 SpringBoot Docker 容器
    5. Docker 容器部署 Nginx
      1. 拉取最新 alpine 版本 Nginx 镜像
      2. 启动默认 nginx:alpine 镜像
      3. 指定配置文件启动 nginx:alpine
      4. 构建自己的 nginx 镜像并启动
      5. nginx.conf 中的用户名必须是 nginx
    6. docker容器部署prometheus
    7. docker容器部署grafana
    8. docker容器部署邮件服务器
    9. 遇到的问题
      1. 容器内的nginx无法访问宿主机网络
      2. 查看容器中的日志
      3. alpine镜像中jvm工具不可用问题
      4. java.lang.UnsatisfiedLinkError: no fontmanager in java.library.path
      5. 修改容器时区
        1. Dockerfile中指定时区
        2. 基于alpine的镜像中修改时区
      6. Mac中使用docker时遇到的问题
        1. mac中映射的文件需要先设置共享
        2. mac上宿主机和容器互相网络访问
        3. M1 Mac 上构建和运行 amd64/x86_64 架构镜像

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论