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

Docker

Docker 学习笔记

Docker 是一个跨平台、可移植并且简单易用的容器解决方案。基于Go语言开发并遵从Apache 2.0协议。
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 镜像是采用分层方式构建的,每个镜像都由一系列的“镜像层”组成
写时复制:每个容器启动时不需要单独复制一份镜像文件,而是将所有镜像层以只读的方式挂载到一个挂载点,在其上覆盖一个可读写的容器层。
内容寻址:对镜像内容计算校验和,生成内容哈希,减少冲突。
联合挂载:联合挂载技术可以在一个挂载点同时挂载多个文件系统,将挂载点的原目录与被挂载的内容进行整合,使得最终可见的文件系统将会包含整合之后的各层的文件和目录。

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

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

container 容器

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

image和container的区别?

docker文件系统UFS

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

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

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 则表示无网络,容器将无法联网


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 类型网络只能在 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.


安装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

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

安装 docker repo

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

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

安装 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 用户组。
建立 docker 组:
sudo groupadd docker
将当前用户加入 docker 组:
sudo usermod -aG docker $USER
退出linux重新登录,不加 sudo 执行docker
docker run hello-world
成功说明当前用户加入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 安装

Homebrew 的 Cask 已经支持 Docker Desktop for Mac,因此可以很方便的使用 Homebrew Cask 来进行安装:
brew cask install 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/

FROM 指定基础镜像

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

RUN 执行命令

RUN 指令是用来执行命令行命令的。
其格式有两种:

  • shell 格式: RUN <命令> 就像直接在命令行中输入的命令一样。 例如 RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec 格式: RUN ["可执行文件", "参数1", "参数2"] 这更像是函数调用中的格式。

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

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 工具下载,处理权限、解压缩、然后清理无用文件更合理。m
因此,这个功能其实并不实用,而且不推荐使用。

CMD 容器启动命令

shell 格式 CMD <命令>
exec 格式 CMD ["可执行文件", "参数1", "参数2"...]
CMD 指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令
一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令。

ENTRYPOINT 入口点

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:
<ENTRYPOINT> "<CMD>"

WORKDIR 指定工作目录

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

EXPOSE 声明端口

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


docker 常用命令

docker info 查看根路径等信息

# 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: yq01-aip-bscc-gpu03.yq01.baidu.com
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

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

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

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

--network指定容器网络模式

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

-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 绑定主机上不存在的文件或目录,则不会自动创建,会产生一个错误。

--privileged

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

容器无法启动问题排查

去掉 -d 后台运行参数,而是 -it 把容器日志打印到前台,看有什么报错。


docker 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 的镜像。

修改docker镜像存储位置

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

解决方法:

软链接

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 批量删除none镜像

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


docker build 构建镜像

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

构建上下文

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 ps 列出本机容器

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

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或容器名> [<仓库名>[:<标签>]]
例如

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

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


docker save 导出镜像

保存镜像到文件
docker save -o myimage.tar myimage:tag
-o 指定保存的镜像的名字

导出镜像并压缩

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

解压导入镜像

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

导出镜像压缩包
docker save myimage:tag | gzip > myimage_tag.tar.gz
例如
docker save myimages:6 | gzip > myimages-6.tar.gz

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

docker load 导入镜像

导入使用 docker save 命令导出的镜像。
--input , -i : 指定导入的文件,代替 STDIN。
--quiet , -q : 精简输出信息。

内网下载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 cp 在容器和本机间拷贝文件

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

docker tag 重命名镜像

docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]
重命名镜像,将 source 重命名为 target

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 container_id/name 查看
如果在容器内部,可以用 ps -fe 查看,其中 1 号进程就是启动命令


docker-compose

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-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

实践

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

启动默认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容器
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

linode 上的 nginx

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

解释:

  • 以 host 网络模式启动容器,和宿主机完全共享网络,不需要再配置 -p 端口映射,否则容器内的 nginx 需要配置主机 ip 才能访问主机网络。
  • 容器中默认是 UTC 时区,比我们的时间慢8小时,改为 Asia/Shanghai 即东八区
  • 文件和文件夹映射
    把本地的配置文件 /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部署spring boot服务

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

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

Spring Boot项目添加Docker支持

在项目根目录下添加 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

构建spring boot镜像

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

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

$ docker build -t eureka .
Sending build context to Docker daemon  77.93MB
Step 1/5 : FROM openjdk:8-jdk-alpine
8-jdk-alpine: Pulling from library/openjdk
e7c96db7181b: Pull complete
f910a506b6cb: Pull complete
c2274a1a0e27: Pull complete
Digest: sha256:94792824df2df33402f201713f932b58cb9de94a0cd524164a0f2283343547b3
Status: Downloaded newer image for openjdk:8-jdk-alpine
 ---> a3562aa0b991
Step 2/5 : VOLUME /tmp
 ---> Running in d2b2b3484ab0
Removing intermediate container d2b2b3484ab0
 ---> d0ee8c49a7f7
Step 3/5 : ARG JAR_FILE=target/*.jar
 ---> Running in 63ca6fb2512f
Removing intermediate container 63ca6fb2512f
 ---> 8db784deccb1
Step 4/5 : COPY ${JAR_FILE} app.jar
 ---> 346c91431891
Step 5/5 : ENTRYPOINT ["java","-jar","/app.jar"]
 ---> Running in 130896b5824c
Removing intermediate container 130896b5824c
 ---> f7cbb2a5f90f
Successfully built f7cbb2a5f90f
Successfully tagged eureka:latest

查看构建好的镜像 docker image ls

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
myapp             latest              448400e97df0        4 seconds ago       180MB

启动spring boot容器

docker run -d --rm \
--network host \
--name myapp \
-v /data/log/spring:/data/log/spring \
-e "SPRING_PROFILES_ACTIVE=local" \
myapp

解释下

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

启动后进入容器 docker exec -it myapp 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/"

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/


上一篇 Python

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

阅读
评论
15,130
阅读预计63分钟
创建日期 2019-08-12
修改日期 2020-07-15
类别
目录
  1. 基本概念
    1. 为什么要用docker?
    2. repository 仓库
    3. image 镜像
      1. 一个容器中修改了基础镜像是否会影响其他容器?
    4. container 容器
      1. image和container的区别?
    5. docker文件系统UFS
      1. volume 卷
    6. docker Daemon与client
    7. docker 网络
      1. bridge
      2. host
  2. 安装Docker
    1. CentOS 安装 Docker CE
      1. 安装 docker repo
      2. yum安装最新版docker
      3. 启动docker并验证
      4. 将当前用户加入docker用户组
    2. Docker升级到指定版本
    3. Mac安装Docker CE
      1. 使用 Homebrew 安装
  3. 构建镜像
    1. Dockerfile
      1. FROM 指定基础镜像
      2. RUN 执行命令
      3. COPY 复制文件
      4. ADD 更高级的复制文件
      5. CMD 容器启动命令
      6. ENTRYPOINT 入口点
      7. WORKDIR 指定工作目录
      8. EXPOSE 声明端口
  4. docker 常用命令
    1. docker info 查看根路径等信息
    2. docker network
      1. docker network create
    3. docker run 运行容器
      1. --network指定容器网络模式
      2. -v绑定文件/文件夹
      3. --mount挂载卷
      4. --privileged
      5. 容器无法启动问题排查
    4. docker pull 下载镜像
      1. 修改docker镜像存储位置
        1. 软链接
        2. 修改 docker 配置文件
        3. 修改docker.service服务配置
      2. docker no space left on device
    5. docker images 列出本机镜像
    6. docker image rm 删除本地镜像
      1. docker 批量删除none镜像
    7. docker build 构建镜像
      1. 构建上下文
      2. .dockerignore
    8. docker ps 列出本机容器
    9. docker container start 启动已终止容器
    10. docker stop 终止容器
    11. docker rm 删除容器
    12. docker exec 进入容器执行命令
      1. OCI runtime exec failed bash executable file not found
    13. docker commit 将容器保存为镜像
    14. docker save 导出镜像
    15. docker load 导入镜像
      1. 内网下载docker镜像
    16. docker cp 在容器和本机间拷贝文件
    17. docker tag 重命名镜像
    18. docker logs 查看容器日志
    19. docker inspect 显示容器信息
      1. 如何查看容器的启动命令
  5. 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网络
  6. docker仓库
    1. docker hub
    2. docker-registry 私人仓库
  7. 实践
    1. docker容器部署nginx
      1. 拉取最新alpine版本nginx镜像
      2. 启动默认nginx:alpine镜像
      3. 指定配置文件启动nginx:alpine
      4. 构建自己的nginx镜像并启动
      5. nginx.conf中的用户名必须是nginx
    2. docker部署spring boot服务
      1. Spring Boot项目添加Docker支持
      2. 构建spring boot镜像
      3. 启动spring boot容器
    3. docker容器部署prometheus
    4. docker容器部署grafana
    5. docker容器部署邮件服务器
    6. 遇到的问题
      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上宿主机和容器互相网络访问

页面信息

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

评论