Docker学习
一、环境准备
- linux系统,本人使用的是centos8,最小化安装
二、安装
下载 docker 脚本
可以通过 get.docker 下载 docker 的安装脚本
直接通过
curl -fsSL get.docker.com -o get-docker.sh
命令下载 docker 脚本
执行脚本
验证
执行
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、容器的交互模式
我们创建一个 ubuntu 容器
1
2
3-it 是指交互式的模式
sh 执行的命令,就是进入到交互式的 ubuntu 的命令行
docker container run -it ubuntu sh这里我们可以看到已经进入到了 ubuntu 的命令行了。
当我们执行 exit 退出后,在查看容器运行情况,可以看到 ubuntu 这个容器已经退出了。
交互式的进入到已经启动的容器中
1
2
3
4exec 执行
-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
- 在本地查找是否有 nginx 这个 image 镜像,但是发现没有
- 去远程的 image registry 查找 nginx 镜像(默认的 registry 是 Docker Hub)
- 下载最新版的 nginx 镜像(nginx:latest 默认)
- 基于 nginx 镜像来创建一个新的容器,并准备运行
- docker engine 分配个这个容器一个虚拟的 IP 地址
- 在宿主机上打开 80 端口并把容器的 80 端口转发到宿主机上
- 启动容器,运行指定的命令
四、镜像的创建使用管理和发布
1、镜像的获取
pull from registry(online)
从 registry 拉取- public 公有
- private 私有
build from Dockerfile(online)
从 Dockerfile 构建load from file(offline)
文件导入(离线)
2、镜像的获取
1.在 dockerhub 或 quay.io 获取
我们先看到一下 image 都有哪些命令
pull 拉取镜像
在 Docker hub 找到要拉取的镜像,执行命令拉取即可。
这里我们可以看到要执行的命令是:
docker pull nginx
,这是早期版本的命令,建议使用:docker image pull nginx
我们还可以拉取别的版本的
这里我们使用:
docker image pull nginx:1.20.1
就可以拉取了。
查看某个镜像的详细信息
使用
docker image inspect imageID
即可删除
使用
docker image rm imageID
即可。需要注意的是,如果镜像有某个容器在使用的话是无法删除的。我必须删除掉使用的容器再能删除
2.导入导出方式
导出
我们可以再别的电脑或者环境中保存一个镜像的文件。
1
2
3
4nginx:1.20.1 要保存的镜像以及版本,如果不添加版本默认latest版本
-o 是指 output 输出
nginx.image 保存的文件名字
docker image save nginx:1.20.1 -o nginx导入
我们先把 1.20.1 版本的删除掉,在进行导入查看
1
2-i 是指 input 输入
docker image load -i nginx
3.Dockerfile 方式
1)什么是 Dockerfile?
- 是用于构建 docker 镜像的文件
- 包含了构建镜像所需的指令
- 有特定的语法规则
2)举例:执行一个 Python 程序
假如我们要在一台 ubuntu 21.04 上运行下面这个 hello.py 的 Python 程序
hello.py 的文件内容:
1 | print("hello docker") |
第一步,准备 Python 的环境
1 | apt-get update && / |
第二步,运行 hello.py
1 | python3 hello.py |
3)一个 Dockerfile 的基本结构
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 官方文档
通过 Dockerfile 构建镜像
由于我的 centos 是一台虚拟机,并且没有做桥接,所以在 centos 中拉下来的镜像网络不通无法进行 apt-get update,所以在我的另一台 kali 系统的电脑上进行的测试,并且后续所有测试都在 kali 上进行
首先我们先将 hello.py 和 Dockerfile 准备好
通过
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 | nginx:1.20.1 要保存的镜像以及版本,如果不添加版本默认latest版本 |
2.push 到 dockerhub 中进行分享
首先要在 dockerhub 注册一个账号,我们的镜像格式都是 id/imageName
在 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 删除
push
在 push **之前我们还需要登录到 **dockerhub,
docker login
这里可以看到我们已经登录成功了
我们可以通过
docker image push sunyubk/hello:1.0
进行 **push **了。push ** 后我们就可以在我们的 **dockerhub 中看到我们 push 的镜像了
拉取测试
我们先将本地的 sunyubk/hello:1.0 这个镜像删除掉在进行拉取
我们将镜像拉取下来,并运行测试
五、Dockerfile
1、基础镜像的选择
- 官方镜像优于非官方镜像,如果没有官方镜像,则尽量选择 Dockerfile 开源的
- 固定版本 tag 而不是每次都是用 latest
- 尽量选择体积小的镜像
2、通过 RUN 执行指令
RUN
主要用于在 image 里执行指令,比如安装软件,下载文件等。
1 | apt-get update |
Dockerfile
1 | FROM ubuntu:21.04 |
镜像的大小和分层
每个 RUN
命令的执行都会产生一层 image layer ,这样会导致镜像比较臃肿。
改进版 Dockerfile
1 | FROM ubuntu:21.04 |
这样编写的 Dockerfile 只会通过一个 RUN
命令执行,只会产生一层。
3、文件复制和目录操作
文件复制
往镜像中复制文件有两种方式,分别是
copy
和add
。复制普通文件
copy
和add
都可以把 local 的一个文件复制到镜像中,如果目标目录不存在,则会自动创建1
2FROM python:3.9.5-alpine3.13
COPY hello.py /app/hello.py复制压缩文件
add
比copy
高级一点的地方就是,如果复制一个 gzip 等压缩文件时,add
会帮助我们去自动解压缩文件。1
2FROM python:3.9.5-alpine3.13
ADD hello.tar.gz /app/如何选择
在
copy
和add
指令的选择的时候,可以遵循这样的原则,所有文件复制均使用copy
,仅在需要自动解压缩的场合使用add
。目录操作
目录切换
WORKDIR
切换目录,如果要切换的目录不存在,则创建目录1
2
3FROM python:3.9.5-alpine3.13
WORKDIR /app
COPY hello.py /hello.py
4、构建参数和环境变量(ARG vs ENV)
ARG
和 ENV
是经常被混淆的两个 Dockerfile 的语法,都可以用来设置一个 变量。但实际上两者会有很多的不同。
1 | FROM ubuntu:21.04 |
ENV
1 | FROM ubuntu:21.04 |
ARG
1 | FROM ubuntu:21.04 |
区别
- 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 所设置的命令是一定会被执行的。 - ENTRYPOINT 和 CMD 可以联合使用,ENTRYPOINT 设置执行的命令,CMD 传递参数
准备三个 Dockerfile,并构建成相应的镜像
Dockerfile-cmd
1
2FROM ubuntu:21.04
CMD ["echo","hello docker"]Dockerfile-entrypoint
1
2FROM ubuntu:21.04
ENTRYPOINT ["echo","hello docker"]Dockerfile-both
1
2
3FROM ubuntu:21.04
ENTRYPOINT ["echo"]
CMD []根据镜像创建容器
1
2-rm 运行后删除容器
docker container run --rm -it imagenamedemo-cmd
在下图中可以看到,如果在创建容器的时候指定命令,则 CMD 中的命令不会运行
demo-entrypoint
在下图中可以看到,如果在创建容器的时候指定指令,指令会作为参数传递进去,原本 Dockerfile-entrypoint 中的 ENTRYPOINT 肯定会执行。
demo-bot
在下图 中可以看到:
第一遍命令什么都没有打印,这是因为 Dockerfile-both 中的 ENTRYPOINT 执行了
echo
,但是并没有设置要打印的值。第二遍命令打印了创建容器是指定的命令,这是因为将指定的命令作为参数传递进去了。
7、Shell 格式和 Exec 格式
CMD 和 ENTRYPOINT 都支持 Shell 格式和 Exec 格式
Shell 格式
1 | CMD echo "hello docker" |
Exec 格式
1 | CMD ["echo","hello docker"] |
注意 Shell 脚本问题
1 | FROM ubuntu:21.04 |
假如我们要把上面的 CMD 改成 Exec 格式,下面这样改是不行的。
1 | FROM ubuntu:21.04 |
它会打印出 hello $NAME
,而不是 hello docker
。这里就需要以 shell 脚本的方式去执行
1 | FROM ubuntu:21.04 |
六、数据持久化
为什么需要数据持久化:因为当我们将容器(cotainer)删除之后,那么我们容器中的数据也会被删除。(数据不随着 Container 的结束而结束)
1、Data volume
Data volume 是 Docker 一个卷 的概念,就是 Docker 管理宿主机文件系统的一部分,默认位于 /var/lib/docker/volumes
目录中。
这里我们使用 mysql 的镜像来演示:
首先我们先准备 mysql:5.7 的镜像
创建容器
关于 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进入容器进行测试
我们交互式的进入到容器的中,并连接进入mysql,并且查看数据库,可以看到数据库是初始化的状态。
我们创建一个数据库,并退出到容器
我们进入容器的
/var/lib/mysql
目录下,也可以看到我们创建的数据库在这里生成了文件夹
退出容器,查看我们本地 volume文件
查看 volume 列表
1
docker volume ls
查看对应的文件信息
1
docker volume inspect mysql_data
这里看到的目录就是与容器中 mysql 目录绑定的目录
我们查看目录下的文件
可以看到,
test
已经在目录下了。
测试:
我们在容器中创建文件/文件夹,会不会同步到对应的volume中?
我们先进入容器中
在持久化的目录中新建一个文件夹
我们在 volume 中产看
可以看到,我们创建的文件夹在 volume 中已经出现了
如果我们将 volume 中的文件删除,容器中会同步么?
我们删除 volume 目录中创建的文件夹
我们回到容器中的目录查看
可以看到,文件夹也不见了
通过这个测试,我们可以看出,volume 帮我们做了一个类似软连接的功能。在容器里面的改动,可以再宿主机中感知,而在宿主机里面的改动,在容器中也可以感知。
注意:
- 如果 volume 是空的,而 container 中有内容,那么 docker 会将 container 中的内容拷贝到 volume 中
- 如果 volume 中已经有内容,则会将 container 中的目录覆盖掉。
命令行小技巧
1、批量操作
我们可以通过
docker container ls -aq
获取到所有 container 的id我们可以通过参数的方式将这些 id,进行一个传递
批量启动
批量停止
批量删除
2、系统清理
- 我们可以使用
docker container prune -f
,来清理掉已经停止的容器 - 我们可以使用
docker image prune -a
,来清理掉没有使用的镜像y