【笔记】Docker

/ 0评 / 0

Docker安装

以Ubuntu/Debian为例,Docker安装可以分为如下几个步骤:

一般包括如下命令:

sudo apt-get update
sudo apt-get install ca-certificates curl

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

注意:国内访问APT源可能会失败,可以通过设置代理解决,临时设置代理的方法是通过-o参数,如:

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -o Acquire::http::proxy="http://127.0.0.1:7890/"

其中的-o Acquire::http::proxy="http://127.0.0.1:7890/"设置了代理。

由于Docker官方的Registry已经被国内网络封禁,因此为了拉取镜像,还需要为Docker设置代理:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo touch /etc/systemd/system/docker.service.d/http-proxy.conf

在http-proxy.conf中添加如下内容:

[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890/"
Environment="HTTPS_PROXY=http://127.0.0.1:7890/"
Environment="NO_PROXY=localhost,127.0.0.1,.example.com"

并重启Docker:

sudo systemctl daemon-reload
sudo systemctl restart docker

Docker入门

使用docker container run命令可以创建一个容器,如

sudo docker run -i -t ubuntu /bin/bash

其中-i表示开启容器中的stdin流,主要是为了创建交互式容器,通常配合-t使用,-t表示Docker创建容器后为其分配一个类似于tty的终端,ubuntu表示容器所使用的镜像,而/bin/bash表示创建容器后要执行的命令,当docker run创建完容器后就会打开容器下的一个bash shell,此时就可以在容器范围内执行命令。由于创建的是交互式容器,因此一旦退出交互式shell容器就会停止,退出的方法是输入exit命令。

如果不想创建交互式容器,可以创建守护式容器,只需要使用-d参数即可,此时run命令将直接返回容器ID,而不会附加到容器当中。

一般所有docker container开头的命令都可以省略container,如docker container run和docker run是一样的,其它前缀一般也可以省略,如docker image开头的命令可以省略image。

可以使用docker container ps或者docker container ls命令列举已创建的容器,默认情况下ps命令只会列举运行中的容器,使用docker ps -a可以列举所有容器,包括停止状态的容器。docker ps -n x可以列举最后x个容器,包括停止状态的容器。

创建容器时Docker会自动为容器分配一个ID,容器内的主机名(hostname)也被设置为这个ID,同时默认情况下Docker也会为容器生成一个随机的名称,可以通过--name参数来手动指定,如

sudo docker run --name myubuntu -i -t ubuntu /bin/bash

容器的命名是唯一的,如果要创建同名容器,必须通过docker container rm命令删除已有的容器。

为了启动容器,可以使用docker container start命令,此时会按照docker container run的配置运行容器,但不会附着到容器当中,可以使用docker container attach命令附着到容器。

也可以使用docker container restart命令来重启容器。

如果想要查看容器内的输出,可以使用docker container logs命令来获取容器的日志,持续监控日志可以加上-f参数,--tail 10参数表示只获取最后10行,而-t可以为每条日志加上时间戳。

使用docker container top命令可以查看容器内部运行的进程,docker container stats命令可以查看容器的信息和状态,包括CPU、内存、网络IO、存储IO等。

使用docker container exec命令可以在容内额外启动新进程,使用-d参数表示创建后台进程。

为了停止容器,可以使用docker container stop命令。

使用docker container inspect命令可以获取容器的配置信息,其中将包括容器的网络相关配置,容器的运行参数等大量深入的配置。

使用docker container rm命令可以删除停止状态的容器,使用-f参数可以强制删除运行中的容器。

Docker镜像

Docker的机制类似于Git,容器通过镜像来创建,而镜像是一个多层虚拟化系统,最底端是一个引导文件系统bootfs,其上有root文件系统rootfs,可能是某种操作系统,如Debian或者Ubuntu,这一层称为基础镜像,基础镜像之上是一个读写文件系统,我们的程序就是在读写文件系统之上运行的。

Docker镜像使用不可变的思想,任何对镜像的操作都会产生一层新的镜像,Docker采用写时复制的机制来创建新的镜像,这样创建一个容器时就相当于从底向上构建一个镜像栈。

可以使用docker images命令(相当于docker image ls命令)来列举当前的本地镜像,这些镜像保存在/var/lib/docker目录下,当运行docker run命令创建镜像时,首先会查找本地是否存在所需的镜像,如果不存在则从网络上下载,Docker的镜像保存在仓库当中,类似于Git,托管平台称为Docker Registry,目前最主要的是Docker官方运营的公共Registry服务,即Docker Hub。

每个人都可以注册Docker Hub账户,并上传自己的镜像,Docker使用者可以从Docker Hub上拉取所需的镜像。

一个仓库可以拥有多个镜像,为了区分,镜像会附带一个标签(tag),如ubuntu:latest,ubuntu:12.04。

Docker Hub有两种类型的仓库,分别是用户仓库和顶层仓库,用户仓库的镜像都由用户自己创建,而顶层仓库是由Docker内部的人管理的,用户仓库的命名往往为username/reponame格式,而顶级仓库的命名为reponame格式,如ubuntu就是一个顶级仓库。

sudo docker run --name myubuntu -i -t ubuntu /bin/bash

当运行上述命令时,首先会检查本地中是否存在ubuntu镜像,注意这里省略了标签名,因此相当于ubuntu:latest。如果本地中没有这个镜像,那么Docker就会尝试在Registry中下载,默认从Docker Hub中查找,ubuntu是一个顶级仓库,因此将定位到顶级仓库中的镜像ubuntu:latest,并下载到本地,随后使用该镜像创建容器。

可以使用docker search命令来查找Docker Hub上公开的可用镜像,如docker search ubuntu。

可以使用docker image pull命令来拉取Docker Hub上的镜像,如docker pull ubuntu:12.04。

每个人都可以构建自己的镜像,构建的方式有两种:

目前推荐使用的是第二种。

首先介绍第一种方法,为了构建镜像,只需要从某一个镜像创建一个容器,并根据自己的需要在容器内增加内容,随后使用docker container commit命令在本地提交,如:

docker commit -m"my ubuntu" -a"white" myubuntu white/ubuntu:latest

docker container commit命令将提交当前镜像上的所有修改,并创建一个新的镜像,其中-m参数指定注释,-a参数指定作者名,myubuntu是容器名,而white/ubuntu:latest是创建的镜像名,latest是标签。commit命令仅仅是本地提交,而且是差异化提交,因此速度很快。

创建镜像之后可以通过docker images ls命令列举当前的本地镜像查看。一旦创建镜像之后,就可以上传到Docker Hub。第一步是使用docker login进行登录,推荐使用如下形式:

docker login -u "myusername" -p "mypassword" docker.io

这里使用密码进行登录,-u参数指定用户名,-p参数指定密码,这里docker.io表示登录到Docker Hub,由于Docker Registry服务是开源的,因此企业也可以搭建内部的Registry。

也可以使用Access Token进行登录,只需要到Docker Hub后台创建密钥即可。登录之后可以使用docker logout注销,但在登录状态下才能上传到Docker Hub。

为了上传,需要到Docker Hub创建一个仓库,并记住仓库的名称,如white/ubuntu,并且保持本地镜像的名称和仓库名一致,如果不一致的话可以使用docker image tag命令创建别名,如:

docke image tag white/myubuntu white/ubuntu

命令中省略了标签,因此都指的是latest。

注意这里是为已有的镜像创建别名,和容器是没关系的,之后使用docker image push命令就可以上传到Docker Hub了:

docker image push white/ubuntu:latest

这里指定的是latest标签,因此将会查找本地中的white/ubuntu:latest镜像并上传。

接下来介绍第二种方式,即docker buildx build命令和Dockerfile方式,Dockerfile是一个文本文件,其中描述了一个镜像的创建过程,使用的是Docker的DSL语言,因此比较灵活方便,表达力也更强。

Dockerfile的范围局限在一个文件夹,这个文件夹称为构建上下文(build context),Dockerfile应当直接放置在构建上下文中,而且名称就是Dockerfile:

mkdir myubuntu
cd myubuntu
touch Dockerfile

Dockerfile的内容例如:

FROM ubuntu:latest
MAINTAINER White "white@example.com"
RUN apt-get update && apt-get install -y nginx
RUN echo 'hello world' > /usr/share/nginx/html/index.html
EXPOSE 80

稍后介绍其中各行的含义,写好Dockerfile的内容之后,在构建上下文中通过docker buildx build命令即可构建镜像:

sudo docker build -t="white/myubuntu" .

[+] Building 24.0s (7/7) FINISHED                                docker:default
 => [internal] load build definition from Dockerfile                       0.0s
 => => transferring dockerfile: 210B                                       0.0s
 => WARN: MaintainerDeprecated: Maintainer instruction is deprecated in f  0.0s
 => [internal] load metadata for docker.io/library/ubuntu:latest           0.0s
 => [internal] load .dockerignore                                          0.0s
 => => transferring context: 2B                                            0.0s
 => [1/3] FROM docker.io/library/ubuntu:latest                             0.0s
 => [2/3] RUN apt-get update && apt-get install -y nginx                  22.9s
 => [3/3] RUN echo 'hello world' > /usr/share/nginx/html/index.html        0.5s 
 => exporting to image                                                     0.4s 
 => => exporting layers                                                    0.4s 
 => => writing image sha256:70092e438ae3bf17f1fe49ddc666809e0cb205c309eb2  0.0s 
 => => naming to docker.io/white09060/myubuntu                             0.0s 
                                                                                
 1 warning found (use --debug to expand):
 - MaintainerDeprecated: Maintainer instruction is deprecated in favor of using label (line 2)

注意build命令需要传入一个路径,这里指定的是'.',表示构建上下文为当前目录,-t="white/myubuntu"表示构建的镜像名称(相当于初始标签)。构建成功之后,将会返回镜像的ID,由于Docker使用不可变镜像的思想,因此Dockerfile中的每一行影响镜像内容的命令都会产生一层新的镜像,如果Dockerfile中的某一行命令执行失败,docker build命令依旧会返回一个镜像ID,代表最后一行成功执行的命令产生的镜像。build命令大体上会按照docker commit的方式提交新镜像。

如果想查看镜像是如何构建出来的,可以使用docker image history命令,如docker image history white/myubuntu

由于Docker的这种分层机制,构建镜像过程中会使用构建缓存来加快构建速度,例如其中的RUN apt-get update && apt-get install -y nginx命令构建的镜像将被缓存,如果修改了后续的命令再构建,那么这一步就不需要再执行了,这会导致Docker不会刷新APT包缓存,如果不想使用缓存,可以在build命令中使用--no-cache参数。

接下来介绍Dockerfile中各行的含义:

主要指令:

Docker容器运行

Docker创建并允许容器的核心命令是docker container run,可以简写为docker run,它支持一系列的参数,由于Docker镜像的不可变特点,很多参数对应的配置一旦确定后就无法更改,只能通过重新创建容器来改变,因此docker run命令执行时的参数就尤为重要。

--name:设置容器的名字,如果不设置那么Docker将自动生成一个名字,在许多命令中,容器名可以取代容器ID。

--cidfile:将容器ID输出到文件,类似于PID文件,如--cidfile /tmp/docker_test.cid。

--pid:设置进程命名空间模式,默认情况下容器都会具有自己的进程命名空间,并且系统进程的PID被屏蔽,因此可以重用系统进程所占用的PID。

--pid参数允许配置容器和主机共享同个命名空间(--pid=host),也可以配置该容器加入另一个容器的命名空间(--pid=container:mynginx)。

--privileged:为容器开启特权模式,一旦开启后容器几乎可以做到主机能做的任何事情,包括Linux内核的所有功能,该参数用于特殊用途,例如在Docker中运行Docker。

-w/--workdir:设置容器的工作目录,相当于Dockerfile中的WORKDIR指令,该参数会覆盖WORKDIR指令。

--storage-opt:设置容器文件系统的大小限制,仅适用于btrfs、overlay2、windowsfilter、zfs存储驱动,如--storage-opt size=120G。

--tmpfs:用于挂载tmpfs文件系统,如--tmpfs /run:rw,noexec,nosuid,size=65536k。

-v:用于挂载卷,如-v ./content:/content,分号左边是主机上的源目录,分号右边是容器内的挂载目录,如果源目录不存在Docker将自动创建,此参数可以配合Dockerfile中的VOLUME指令使用。

--read-only:设置容器根文件系统为只读模式,容器将无法对文件系统进行任何写操作,但在挂载卷时可以将特定的卷设置为可读写模式,这样容器就只能在特定的卷写入数据。

--mount:用于挂载文件系统。包括卷、主机目录和tmpfs,如--mount type=bind,src=/data,dst=/data。

-p/--expose:用于公开容器的网络端口,可以将容器内的特定端口映射到主机上的特定端口。

如-p 127.0.0.1:80:8080/tcp表示将容器内的8080端口映射到127.0.0.1(主机)上的80端口,并且是TCP协议。

如果省略IP地址,即-p 80:8080/tcp则表示将容器内的8080端口映射到0.0.0.0上的80端口,此时外部可以访问该端口。

如果不需要映射,可以写成--expose 80表示公开容器内的80端口,但不映射到主机。

-P/--expose-all:用于公开容器内用到的所有网络端口,此参数可以配合Dockfile中的EXPOSE指令使用,EXPOSE指令的所有端口都会因该参数被公开。

--pull:设置镜像构建(运行)时的拉取策略,默认情况下为--pull=mssing表示先查找本地缓存中的镜像,如果不存在才拉取镜像。

其它可选的值为--pull=never表示只查找本地缓存中的镜像,如果不存在也不会拉取,而是抛出错误。

--pull=always表示总是拉取镜像,从不查找本地缓存。

-e/--env/--env-file:设置环境变量,相当于Dockerfile中的ENV指令,如--env VAR1=value1 --env VAR2=value2,如果省略变量值,Docker首先会从主机环境变量中查找,如果不存在则该变量将被忽略。可以从文件中导入环境变量,如--env-file env.list。

-l/--label/--label-file:设置容器的元数据,相当于Dockerfile中的LABEL指令,格式和环境变量类似。

--network:设置容器所连接的网络,如--network=my-net --ip=192.0.2.69。

--volumes-from:用于挂载另一个容器所挂载的所有卷,如--volumes-from 777f7dc92da7 --volumes-from ba8c0c54f0f2:ro。

-d/--detach:以分离模式运行容器,即创建守护式容器。docker run -d命令将直接返回容器的ID,并且不会监控容器内的输入输出,注意不可以在分离模式中运行service x start命令,否则容器会退出,即docker run -d -p 80:80 myimage service nginx start是错误的,应该使用:

docker run -d -p 80:80 my_image nginx -g 'daemon off;'

--device:用于将主机上的设备添加到容器当中,默认情况下容器可以使用read、write、mknod设备,如 --device=/dev/sdc:/dev/xvdc。

-a/--attach:以附加模式运行容器,用于绑定容器内的stdin、stdout、stderr流,如-a stdin。

-i/--interactive:保证容器内的stdin流打开,用于创建交互式容器,通常配合-t使用。

-t/--tty:为容器分配一个TTY伪终端,通常配合-i使用,如docker run -i -t debian passwd root。

如果只使用-i参数而不使用-t参数,那么TTY的相关功能将无法使用,例如上述修改root密码时,密码将会以纯文本显示。

如果只使用-t参数而不使用-i参数,TTY终端将无法写入stdin,一般情况下没有意义。

--restart:设置容器的重启策略,默认为--restart=no,表示从不自动重启。

--restart=on-failure[:max-retries]表示在出现错误时重启(退出码非零),可以设置最大重启次数,如--restart=on-failure:5。

--restart=unless-stopped表示总是重启,但容器被显式停止或Docker引擎被停止或重启除外。

--restart=always表示总是且无限期地重启。

--rm:默认情况下容器的文件系统在容器退出时保持不变,使用--rm参数可以指示Docker在容器退出时清理文件系统中的数据。

--add-host:为容器的hosts文件增加内容,如--add-host=my-hostname=8.8.8.8。

--log-driver:设置容器的日志驱动程序,默认为--log-driver=json-file,如果想关闭容器的日志,可以使用--log-driver=none。

--ulimit:设置容器的ulimit,在默认配置下,容器的权限不足以在容器中设置ulimit。

--stop-signal:设置停止容器时发送什么系统调用信号给容器,相当于Dockerfile中的STOPSIGNAL。

--security-opt:设置容器的安全配置,不述,可见Optional security options

--stop-timeout:设置停止容器时发送系统调用信号后等待的时间(秒),如果容器在超时后没有退出,将发送SIGKILL信号,Linux容器的默认值为--stop-timeout=10,Windows容器的默认值为--stop-timeout=30。

-m/-memory:设置容器的内存上限,如-m 2GB。

Docker容器存储

默认情况下,Docker容器的数据都保存在可读写的文件系统层之上,当容器被删除时,其中的数据也会丢失,而且让一个进程去访问容器中的数据是比较困难的。Docker为容器数据的可持久化提供了两种选择,分别是卷挂载(Volume mount)绑定挂载(Bind mount)

卷通常保存在主机系统中,并由Docker进行管理,非Docker进程不允许修改主机上的卷,而绑定挂载也保存在主机系统中,但可以任意选择目录位置,并且允许非Docker进程修改其中的内容,还有一种临时挂载(tmpfs mount)方式,它保存在主机系统的内存当中,并且从不持久化。

卷挂载和绑定挂载都可以使用-v/--volume参数配置,临时挂载可以使用--tmpfs参数配置,这三种方式都可以使用--mount参数配置,这也是Docker官方推荐的。

卷是由Docker进行管理的,可以通过docker volume开头的命令进行操作,创建卷的命令是docker volume create,也可以在执行docker container run命令的时候创建卷。同个卷可以被多个容器同时使用,即便没有容器使用卷,该卷也不会被删除(除非使用了--rm参数),可以通过docker volume prune命令删除所有未使用的卷。

卷分为命名卷和匿名卷,例如通过Dockerfile中的VOLUME指令断言的卷没有在执行docker run命令时配置挂载源时,Docker会自动为其创建匿名卷,匿名卷会生成一个很长的名字,同样地删除容器时并不会删除所使用的匿名卷(除非使用了--rm参数)。

绑定挂载和卷挂载类似,但功能比较局限,只是简单地将主机上的某个目录挂载到容器当中,主机上的任何进程或者(默认情况下)容器都可以对该目录进行读写,绑定挂载是一次性的,不需要通过Docker管理。相比之下,卷具有如下优点:

一般情况下,卷挂载是首选的,而绑定挂载主要用于挂载配置文件,例如Docker默认会将/etc/resolv.conf绑定挂载到容器中以提供DNS服务。

卷的相关命令:

为了使用所创建的卷,可以使用--mount参数,如--mount source=myvol,target=/app,其中source表示所使用的卷,而target表示挂载的目标目录。

使用docker run命令启动容器的时候,如果创建了新卷且目标目录中有文件,Docker会将这些文件填充到容器中,使用该卷的其它容器也可以使用这些填充的文件。

在挂载卷的时候,可以设置卷的读写模式,例如只读模式:--mount source=nginx-vol,destination=/usr/share/nginx/html,readonly

--mount参数可以指定挂载的类型,如--mount type=bind/tmpfs,默认情况下为--mount type=volume

注意,type=bind的时候,如果目标目录中有文件,这些文件会被Docker遮蔽,这种行为十分怪异,和挂载卷不同。

type=tmpfs的时候,容器停止时挂载就会被删除,注意tmpfs是无法在多个容器之间共享的。

Docker容器网络

默认情况下,容器会从其主机的网络以某种方式(驱动)分配,容器内部并不知道关于主机网络的任何信息,它仅仅被分配一个IP地址、网关、路由表和DNS服务。Docker支持以下网络驱动:

在执行docker container run命令的时候,可以使用--network container:<name|id>参数将容器附加到另一个容器的网络中。--network参数可以多次设置,以便让容器加入到多个网络,对于正在运行的容器,可以使用docker network connect命令将其连接到特定的网络。

默认情况下容器将使用和主机一样的DNS服务器,可以通过--dns参数设置,如--dns=1.1.1.1。

默认情况下,容器使用bridge模式,即桥接模式,这会导致容器连接到一个默认的软桥,为了提供更好的隔离性,用户可以预定义软桥,通过docker network开头的命令。如:

docker network create -d bridge my-bridge-network

docker network create支持如下参数:

使用docker network rm命令可以删除某个特定的网络,但必须将该网络从所有容器中断开,该命令需要传入网络名称或者ID。而docker network prune将删除所有未使用的网络,即没有任何容器连接的网络。使用docker network ls命令可以列举所有预定义的网络。

可以通过docker network connect和docker network disconnect命令动态地将容器连接到某个网络(也可以通过docker run --network实现)或者从某个网络中断开。如:

docker network connect multi-host-network container1

可以使用--ip/--ip6参数指定IP地址:

docker network connect --ip 10.10.36.122 multi-host-network container2

如果使用host网络驱动,即主机模式,那么容器将会和主机共享相同的网络空间,容器不会被分配一个新的IP地址,注意在主机模式下,容器内所有开放的端口都可以通过主机网络中的对应端口进行访问,此时docker run中的-p和-P参数都会被忽略。主机模式通常比桥接模式的性能更高,因为它不需要NAT转换,但目前主机模式并不支持IPV6。

如果使用none网络驱动,即无网络模式,容器将无法访问其它容器以及主机上的网络,完全隔离。

 

 

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *