Docker学习

一、环境准备

  • linux系统,本人使用的是centos8,最小化安装

二、安装

  1. 下载 docker 脚本

    • 可以通过 get.docker 下载 docker 的安装脚本

      图片加载失败

    • 直接通过 curl -fsSL get.docker.com -o get-docker.sh 命令下载 docker 脚本

      图片加载失败

  2. 执行脚本

    图片加载失败

  3. 验证

    • 执行 docker version 查看 docker 版本

      图片加载失败

      在上图中我们可以看到 docker 的版本等信息,并且可以看到 docker 没有连接。这是因为没有启动 docker 的服务

    • 执行 systemctl start docker 启动 docker 的服务

      图片加载失败

      启动后我们已经可以看到 docker 的服务的相关信息了

到这里,docker 就已经安装完成了

三、容器的快速上手

1、docker 的命令行

  • 我们可以通过 docker 命令查看都有什么可以执行的命令

    图片加载失败

  • docer 命令行的结构:

    1
    docker 想要操作的对象

    例如:

    • 我们可以输入 docker container --help 查看都有什么可以执行的命令

      图片加载失败

      1
      2
      3
      4
      5
      # 列出当前运行的容器
      docker container ls

      # 列出所有容器
      docker container ls -a
    • 我们也可以输入 docker container ls --help 来查看都有什么参数

      图片加载失败

      Aliases:ls 可以替换成什么

      Options:可以使用的参数选项

2、docker 的镜像 VS 容器

1.image 镜像

  • Docker image 是一个 read only 文件
  • 这个文件包含文件系统,源码,库文件,依赖,工具等一些运行 application 所需要的文件
  • 可以理解成一个模板
  • Docker image 具有分层的概念

2.container 容器

  • 可以理解成:“一个运行中的 docker image
  • 实质是复制 image 并在 image 最上层加上一层 read write 的层(称之为 container layer,容器层)
  • 基于同一个 image 可以创建多个 container

3.docker image 的获取

  • 自己制作
  • registry 拉取(比如 docker hub

3、容器的基本操作

1.创建一个容器

我们可以通过 docker container run [image_REPOSITORY] 来创建一个容器

图片加载失败

这里我们可以看到已经成功运行了,我们可以重新打开一个窗口查看一下正在运行的 container

图片加载失败

2.停止容器

我们可以通过 docker container stop [IDs or NAMES] 或者 ctrl + c 来停止一个容器

注意:windows 中使用 ctrl + c并不能停止容器,实际上还是在后台运行的

图片加载失败

3.重新启动停止的容器

我们可以通过 docker container start [IDs or NAMES] 来重新启动容器

图片加载失败

4.删除容器

我们可以通过 docker container rm [IDs or NAMES] 来删除一个容器

首先,我们查看所有容器:

图片加载失败

我们可以看到,这里有一个已经停止了的容器,接下来我们删除掉这个容器,再查看一遍

图片加载失败

通过上图我们可以看到容器已经被删除了。

4、容器的两种模式(attached、detached)

1.attached

这里我们使用 docker container run -p 80:80 nginx 来创建一个容器,-p 80:80 的意思是将容器的内部端口映射到外部端口上。

图片加载失败

我们可以看到已经启动成功了。

接下来,我们在浏览器访问一下虚拟机的 80 端口

图片加载失败

图片加载失败

可以看到,可以成功访问,并且已经打印了相关日志

其实,attached 模式就是,我们的命令会与容器共享。就像我们在 linux 或者 mac 的系统上创建一个 docker 容器后,会显式的显示在窗口中,当我们执行 ctrl + c 后,容器也会停止,这就是 attached 模式

2.detached

这里我们使用 docker container run -d -p 80:80 nginx 来创建一个容器。其中 -d 是指使用 detached 模式启动。

图片加载失败

这里我们可以看到,我们只能看到容器的 id,并不能看到相关一些信息,也可以看到容器已经成功自动了。

接下来,我们在浏览器访问一下虚拟机的 80 端口,就可以看到相关的日志信息并没有打印。

如果,我现在想 attach 到容器中该怎么做呢?

我们可以执行 docker container attach [id or names]**,来 **attach 到我们的容器。

图片加载失败

通过上图我们可以看到已经成功 attach 到我们的容器中了

如果我们不想 attach 到我们的容器中,并且还想看日志怎么办呢?

这时,我们可以执行 docker container logs -f [ids or NAMES] 来查看日志。**-f** 是动态的跟踪我们的日志

图片加载失败

5、容器的交互模式

  1. 我们创建一个 ubuntu 容器

    1
    2
    3
    # -it 是指交互式的模式
    # sh 执行的命令,就是进入到交互式的 ubuntu 的命令行
    docker container run -it ubuntu sh

    图片加载失败

    这里我们可以看到已经进入到了 ubuntu 的命令行了。

    当我们执行 exit 退出后,在查看容器运行情况,可以看到 ubuntu 这个容器已经退出了。

    图片加载失败

  2. 交互式的进入到已经启动的容器中

    1
    2
    3
    4
    # exec 执行
    # -it 交互模式
    # sh 命令行
    docker container exec -it 79 sh

    这里可以看到我们已经进来了。

    图片加载失败

    这个时候我们再次执行 exit,并查看容器的启动情况。

    图片加载失败

    这是因为,我们只是退出了交互式的shell,并不影响这个容器。因为我们交互式的创建一个容器和交互式的进入某个容器是不一样的

6、容器和虚拟机的区别(Container vs VM)

图片加载失败

虚拟机是在 hypervisor 虚拟化层上创建一整个操作系统,而容器是在 Container Engine 容器引擎上的程序,就是一个进程。

容器不是Mini虚拟机:

  • 容器其实是进程
  • 容器中的进程被限制了对 CPU 内存等资源的访问
  • 当进程结束后,容器就退出了。

通过 docker container top [id name] 来查看容器运行了那些进程

图片加载失败

使用 pstree -halps id 查看进程的依赖关系,使用 yum install psmisc 或者 apt-get install psmisc 安装

图片加载失败

图片加载失败

7、docker container run 容器创建的背后发生了什么?

docker container run -d -p 80:80 --name test nginx

  1. 在本地查找是否有 nginx 这个 image 镜像,但是发现没有
  2. 去远程的 image registry 查找 nginx 镜像(默认的 registryDocker Hub
  3. 下载最新版的 nginx 镜像(nginx:latest 默认)
  4. 基于 nginx 镜像来创建一个新的容器,并准备运行
  5. docker engine 分配个这个容器一个虚拟的 IP 地址
  6. 在宿主机上打开 80 端口并把容器的 80 端口转发到宿主机上
  7. 启动容器,运行指定的命令

四、镜像的创建使用管理和发布

1、镜像的获取

  • pull from registry(online)registry 拉取
    • public 公有
    • private 私有
  • build from Dockerfile(online)Dockerfile 构建
  • load from file(offline) 文件导入(离线)

Dockerhub 官网Red Hat quay

2、镜像的获取

1.在 dockerhub 或 quay.io 获取

  1. 我们先看到一下 image 都有哪些命令

    图片加载失败

  2. pull 拉取镜像

    • Docker hub 找到要拉取的镜像,执行命令拉取即可。

      图片加载失败

      这里我们可以看到要执行的命令是:docker pull nginx,这是早期版本的命令,建议使用:docker image pull nginx

    • 我们还可以拉取别的版本的

      图片加载失败

      这里我们使用:docker image pull nginx:1.20.1 就可以拉取了。

  3. 查看某个镜像的详细信息

    使用 docker image inspect imageID 即可

  4. 删除

    使用 docker image rm imageID 即可。

    需要注意的是,如果镜像有某个容器在使用的话是无法删除的。我必须删除掉使用的容器再能删除

2.导入导出方式

  1. 导出

    我们可以再别的电脑或者环境中保存一个镜像的文件。

    1
    2
    3
    4
    # nginx:1.20.1 要保存的镜像以及版本,如果不添加版本默认latest版本
    # -o 是指 output 输出
    # nginx.image 保存的文件名字
    docker image save nginx:1.20.1 -o nginx

    图片加载失败

  2. 导入

    我们先把 1.20.1 版本的删除掉,在进行导入查看

    图片加载失败

    1
    2
    # -i 是指 input 输入
    docker image load -i nginx

    图片加载失败

3.Dockerfile 方式

1)什么是 Dockerfile?

  • 是用于构建 docker 镜像的文件
  • 包含了构建镜像所需的指令
  • 有特定的语法规则

2)举例:执行一个 Python 程序

假如我们要在一台 ubuntu 21.04 上运行下面这个 hello.pyPython 程序

hello.py 的文件内容:

1
print("hello docker")

第一步,准备 Python 的环境

1
2
apt-get update && /
DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev

第二步,运行 hello.py

1
2
$ python3 hello.py
输入:hello docker

3)一个 Dockerfile 的基本结构

  1. Dockerfile

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 引入 ubuntu:21.04 镜像
    FROM ubuntu:21.04
    # 执行命令安装 python 的环境
    RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y python3.9 python3-pip python3.9-dev
    # 将本地的 hello.py 文件添加到 / 根目录下
    ADD hello.py /
    # 执行命令
    CMD ["python3","/hello.py"]

    详细可见 Dockerfile 官方文档

    图片加载失败

  2. 通过 Dockerfile 构建镜像

    由于我的 centos 是一台虚拟机,并且没有做桥接,所以在 centos 中拉下来的镜像网络不通无法进行 apt-get update,所以在我的另一台 kali 系统的电脑上进行的测试,并且后续所有测试都在 kali 上进行

    • 首先我们先将 hello.pyDockerfile 准备好

      图片加载失败

    • 通过 docker image build 进行构建

      1
      2
      3
      4
      5
      6
      # -f DockerFile 的名字
      # -t 构建后镜像名称
      # . 当前目录下
      docker image build -t hello .

      docker image build -f DockfileName -t Tag .

      图片加载失败

    • 可以看到镜像已经构建成功了

      图片加载失败

    • 使用构建的镜像创建容器

      图片加载失败

      可以看到,hello docker 已经打印出来了,并且容器已经退出了。

      图片加载失败

    • 为甚么直接退出了?

      因为 Dockerfile 中的 CMD ["python3","/hello.py"] 执行完之后就停了,这个进程停了,容器自然就停了

4、通过 container 容器生成一个镜像

我们可以使用 docker container commit contaienrId ImageName 命令,根据 container 生成一个镜像

图片加载失败

3、镜像的分享

1.可以通过导出的方式进行分享

1
2
3
4
# nginx:1.20.1 要保存的镜像以及版本,如果不添加版本默认latest版本
# -o 是指 output 输出
# nginx.image 保存的文件名字
docker image save nginx:1.20.1 -o nginx

2.push 到 dockerhub 中进行分享

  1. 首先要在 dockerhub 注册一个账号,我们的镜像格式都是 id/imageName

  2. push 之前我们的镜像 tag 要符合我们 dockerhub 的规范

    • 我们可以通过重新构建来修改镜像的 tag

      1
      docker image build -t DockerhubID/imageName:version .
    • 我们还可以通过已经存在的镜像复制一个新的镜像

      1
      docker image tag hello sunyubk/hello:1.0

      图片加载失败

    注意:通过以上两种方式生成的新的镜像的 id 是相同的,无法根据 id 进行删除,所以我们可以通过 image:tag 删除

  3. push

    • push **之前我们还需要登录到 **dockerhubdocker login

      图片加载失败

      这里可以看到我们已经登录成功了

    • 我们可以通过 docker image push sunyubk/hello:1.0 进行 **push **了。

      图片加载失败

    • push ** 后我们就可以在我们的 **dockerhub 中看到我们 push 的镜像了

      图片加载失败

  4. 拉取测试

    • 我们先将本地的 sunyubk/hello:1.0 这个镜像删除掉在进行拉取

      图片加载失败

    • 我们将镜像拉取下来,并运行测试

      图片加载失败

五、Dockerfile

1、基础镜像的选择

  • 官方镜像优于非官方镜像,如果没有官方镜像,则尽量选择 Dockerfile 开源的
  • 固定版本 tag 而不是每次都是用 latest
  • 尽量选择体积小的镜像

2、通过 RUN 执行指令

RUN 主要用于在 image 里执行指令,比如安装软件,下载文件等。

1
2
3
4
5
6
$ apt-get update
$ apt-get install wget
$ wget wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linxu_amd64.tar.gz
$ tar -zxvf ipinfo_2.0.1_linxu_amd64.tar.gz
$ mv ipinfo_2.0.1_linxu_amd64.tar.gz /usr/bin/ipinfo
$ rm -rf ipinfo_2.0.1_linxu_amd64.tar.gz

Dockerfile

1
2
3
4
5
6
7
FROM ubuntu:21.04
RUN apt-get update
RUN apt-get install wget
RUN wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linxu_amd64.tar.gz
RUN tar -zxvf ipinfo_2.0.1_linxu_amd64.tar.gz
RUN ipinfo_2.0.1_linxu_amd64.tar.gz /usr/bin/ipinfo
RUN rm -rf ipinfo_2.0.1_linxu_amd64.tar.gz

镜像的大小和分层

每个 RUN 命令的执行都会产生一层 image layer ,这样会导致镜像比较臃肿。

改进版 Dockerfile

1
2
3
4
5
6
7
FROM ubuntu:21.04
RUN apt-get update $$ \
apt-get install wget $$ \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linxu_amd64.tar.gz $$ \
tar -zxvf ipinfo_2.0.1_linxu_amd64.tar.gz $$ \
mv ipinfo_2.0.1_linxu_amd64.tar.gz /usr/bin/ipinfo $$ \
rm -rf ipinfo_2.0.1_linxu_amd64.tar.gz

这样编写的 Dockerfile 只会通过一个 RUN 命令执行,只会产生一层。

3、文件复制和目录操作

  1. 文件复制

    往镜像中复制文件有两种方式,分别是 copyadd

    复制普通文件

    copyadd 都可以把 local 的一个文件复制到镜像中,如果目标目录不存在,则会自动创建

    1
    2
    FROM python:3.9.5-alpine3.13
    COPY hello.py /app/hello.py

    复制压缩文件

    addcopy 高级一点的地方就是,如果复制一个 gzip 等压缩文件时,add 会帮助我们去自动解压缩文件。

    1
    2
    FROM python:3.9.5-alpine3.13
    ADD hello.tar.gz /app/

    如何选择

    copyadd 指令的选择的时候,可以遵循这样的原则,所有文件复制均使用 copy,仅在需要自动解压缩的场合使用 add

  2. 目录操作

    目录切换

    WORKDIR 切换目录,如果要切换的目录不存在,则创建目录

    1
    2
    3
    FROM python:3.9.5-alpine3.13
    WORKDIR /app
    COPY hello.py /hello.py

4、构建参数和环境变量(ARG vs ENV)

ARGENV 是经常被混淆的两个 Dockerfile 的语法,都可以用来设置一个 变量。但实际上两者会有很多的不同。

1
2
3
4
5
6
7
FROM ubuntu:21.04
RUN apt-get update $$ \
apt-get install wget $$ \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-2.0.1/ipinfo_2.0.1_linxu_amd64.tar.gz $$ \
tar -zxvf ipinfo_2.0.1_linxu_amd64.tar.gz $$ \
mv ipinfo_2.0.1_linxu_amd64.tar.gz /usr/bin/ipinfo $$ \
rm -rf ipinfo_2.0.1_linxu_amd64.tar.gz

ENV

1
2
3
4
5
6
7
8
FROM ubuntu:21.04
ENV VERSION=2.0.1
RUN apt-get update $$ \
apt-get install wget $$ \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linxu_amd64.tar.gz $$ \
tar -zxvf ipinfo_${VERSION}_linxu_amd64.tar.gz $$ \
mv ipinfo_${VERSION}_linxu_amd64.tar.gz /usr/bin/ipinfo $$ \
rm -rf ipinfo_${VERSION}_linxu_amd64.tar.gz

ARG

1
2
3
4
5
6
7
8
FROM ubuntu:21.04
ARG VERSION=2.0.1
RUN apt-get update $$ \
apt-get install wget $$ \
wget https://github.com/ipinfo/cli/releases/download/ipinfo-${VERSION}/ipinfo_${VERSION}_linxu_amd64.tar.gz $$ \
tar -zxvf ipinfo_${VERSION}_linxu_amd64.tar.gz $$ \
mv ipinfo_${VERSION}_linxu_amd64.tar.gz /usr/bin/ipinfo $$ \
rm -rf ipinfo_${VERSION}_linxu_amd64.tar.gz

区别

图片加载失败

  • ARG 使用的范围是 Dockerfile 构建的阶段,构建后则无法使用了,因为这个变量不会保存在镜像中。
  • ENV 使用的范围不仅是在 Dockerfile 构建的阶段,而且这个变量会作为 环境变量 保存在镜像中,当我们使用这个镜像去创建容器的时候也可以使用这个变量

ARG 还可一在构建的时候动态的修改值, ENV 则不可以

图片加载失败

例如:

1
docker image build -f ./Dockerfile-arg -t ipinfo-arg-2.0.0 --build-arg VERSION=2.0.0 .

5、CMD 容器启动命令

CMD 可以用来设置容器启动时默认会执行的命令。

  • 容器启动时默认执行的命令
  • 如果 docker container run 启动容器是指定了其它命令,则 CMD 命令会被忽略
  • 如果定义了多个 CMD ,只有最后一个会被执行

6、ENTRYPOINT 容器启动命令

ENTRYPOINT 也可以设置容器启动时要执行的命令,但是和 CMD 是有区别的。

  • CMD 设置的命令,可以再 docker container run 时传入其他命令,覆盖掉 CMD 命令,但是 ENTRYPOINT 所设置的命令是一定会被执行的。
  • ENTRYPOINTCMD 可以联合使用,ENTRYPOINT 设置执行的命令,CMD 传递参数
  1. 准备三个 Dockerfile,并构建成相应的镜像

    Dockerfile-cmd

    1
    2
    FROM ubuntu:21.04
    CMD ["echo","hello docker"]

    Dockerfile-entrypoint

    1
    2
    FROM ubuntu:21.04
    ENTRYPOINT ["echo","hello docker"]

    Dockerfile-both

    1
    2
    3
    FROM ubuntu:21.04
    ENTRYPOINT ["echo"]
    CMD []

    图片加载失败

  2. 根据镜像创建容器

    1
    2
    # -rm 运行后删除容器
    docker container run --rm -it imagename

    demo-cmd

    在下图中可以看到,如果在创建容器的时候指定命令,则 CMD 中的命令不会运行

    图片加载失败

    demo-entrypoint

    在下图中可以看到,如果在创建容器的时候指定指令,指令会作为参数传递进去,原本 Dockerfile-entrypoint 中的 ENTRYPOINT 肯定会执行。

    图片加载失败

    demo-bot

    在下图 中可以看到:

    第一遍命令什么都没有打印,这是因为 Dockerfile-both 中的 ENTRYPOINT 执行了 echo ,但是并没有设置要打印的值。

    第二遍命令打印了创建容器是指定的命令,这是因为将指定的命令作为参数传递进去了。

    图片加载失败

7、Shell 格式和 Exec 格式

CMDENTRYPOINT 都支持 Shell 格式和 Exec 格式

Shell 格式

1
2
CMD echo "hello docker"
ENTRYPOINT echo "hello docker"

Exec 格式

1
2
CMD ["echo","hello docker"]
ENTRYPOINT ["echo","hello docker"]

注意 Shell 脚本问题

1
2
3
FROM ubuntu:21.04
ENV NAME=docker
CMD echo "hello $NAME"

假如我们要把上面的 CMD 改成 Exec 格式,下面这样改是不行的。

1
2
3
FROM ubuntu:21.04
ENV NAME=docker
CMD CMD ["echo","hello $NAME"]

它会打印出 hello $NAME,而不是 hello docker。这里就需要以 shell 脚本的方式去执行

1
2
3
FROM ubuntu:21.04
ENV NAME=docker
CMD CMD ["sh","-c","echo hello $N"]

六、数据持久化

为什么需要数据持久化:因为当我们将容器(cotainer)删除之后,那么我们容器中的数据也会被删除。(数据不随着 Container 的结束而结束)

1、Data volume

Data volumeDocker 一个 的概念,就是 Docker 管理宿主机文件系统的一部分,默认位于 /var/lib/docker/volumes 目录中。

这里我们使用 mysql 的镜像来演示:

  1. 首先我们先准备 mysql:5.7 的镜像

    图片加载失败

  2. 创建容器

    关于 MySQL 镜像的使用可以参考:dockerhub中mysql

    1
    2
    3
    -e 设置mysql root用户的密码
    -v 使用volume持久化数据 volume 名称:容器内目录,如果不指定则会默认生成一个随机的文件
    docker container run -d --name mysql_5.7 -e MYSQL_ROOT_PASSWORD=root -v mysql_data:/var/lib/mysql mysql:5.7

    图片加载失败

  3. 进入容器进行测试

    • 我们交互式的进入到容器的中,并连接进入mysql,并且查看数据库,可以看到数据库是初始化的状态。

      图片加载失败

    • 我们创建一个数据库,并退出到容器

      图片加载失败

    • 我们进入容器的 /var/lib/mysql 目录下,也可以看到我们创建的数据库在这里生成了文件夹

      图片加载失败

  4. 退出容器,查看我们本地 volume文件

    • 查看 volume 列表

      1
      docker volume ls

      图片加载失败

    • 查看对应的文件信息

      1
      docker volume inspect mysql_data

      图片加载失败

      这里看到的目录就是与容器中 mysql 目录绑定的目录

    • 我们查看目录下的文件

      图片加载失败

      可以看到,test 已经在目录下了。

    测试:

    1. 我们在容器中创建文件/文件夹,会不会同步到对应的volume中?

      • 我们先进入容器中

      • 在持久化的目录中新建一个文件夹

        图片加载失败

      • 我们在 volume 中产看

        图片加载失败

        可以看到,我们创建的文件夹在 volume 中已经出现了

    2. 如果我们将 volume 中的文件删除,容器中会同步么?

      • 我们删除 volume 目录中创建的文件夹

      • 我们回到容器中的目录查看

        图片加载失败

        可以看到,文件夹也不见了

    通过这个测试,我们可以看出,volume 帮我们做了一个类似软连接的功能。在容器里面的改动,可以再宿主机中感知,而在宿主机里面的改动,在容器中也可以感知。

    注意:

    • 如果 volume 是空的,而 container 中有内容,那么 docker 会将 container 中的内容拷贝到 volume
    • 如果 volume 中已经有内容,则会将 container 中的目录覆盖掉。

命令行小技巧

1、批量操作

  1. 我们可以通过 docker container ls -aq 获取到所有 container 的id

    图片加载失败

  2. 我们可以通过参数的方式将这些 id,进行一个传递

    • 批量启动

      图片加载失败

    • 批量停止

      图片加载失败

    • 批量删除

      图片加载失败

2、系统清理

  1. 我们可以使用 docker container prune -f,来清理掉已经停止的容器
  2. 我们可以使用 docker image prune -a,来清理掉没有使用的镜像y