Docker
Docker架构的工作流程
- 构建镜像:通过编写dockerfile来进行构建
- 推送镜像到仓库:将镜像上传到Docker Hub或私有注册表中
- 拉取镜像:通过docker pull从从仓库中拉取镜像
- 运行容器:使用镜像创建并启动容器
- 管理容器:使用Docker客户端命令管理正在运行的容器,如查看日志、停止容器、查看资源使用情况等
- 网络与存储:容器之间通过Docker网络连接,数据通过Docker卷或绑定挂载进行持久化
组成部分
- docker client:客户端,用户可通过命令行界面与Docker守护进程交互,发送构建、运行、停止容器等相关命令。如docker build
- docker Daemon:守护进程,在宿主机上持续运行的后台服务,负责接收客户端命令,管理容器生命周期、镜像构建和网络/存储等核心组件
- docker image:镜像,一种分层结构的只读模板,包含应用代码、环境、依赖等等。如openjdk:17-jdk-slim
- docker Container:容器,镜像的运行实例,具有独立的文件系统和网络空间
- Registry:仓库,镜像的存储中心,如Docker Hub(公共)或Harbor(私有仓库)
- Volumes:卷,持久化的存储机制,可用于容器中的数据保存(比如使用MongoDB的数据目录啥的)
- Networks:网络,容器间通信的虚拟网络(有默认桥接、host、overlay等模式)
Docker Client
-
具体作用:所谓的客户端,就是用户与Docker守护进程交互的命令行界面,也是我们用户与系统的主要交互方式,用户通过Docker的命令行发出命令,然后这些命令被发送到Docker的守护进程,由守护进程来执行响应操作。
-
交互方式:Docker的客户端与Docker的守护进程之间通过REST API 或者Unix套接字通信。而我们常用的命令是docker,通过它我们可以发出各种各样的Docker操作命令。
-
具体例子
运行容器: docker run <镜像id> 列出正在运行的容器: docker ps 根据dockerfile构建镜像: docker build -t <镜像id> . 进入容器内部执行命令: docker exec -it <容器id> <要在容器内执行的命令>
Docker Daemon
-
具体作用:守护进程是Docker架构的核心,因为它负责管理容器的生命周期、构建镜像、分发镜像等任务。而守护进程通常以后台进程的方式运行,等待来自Docker客户端的API请求。
- 启动和停止容器
- 构建、拉取和推送镜像
- 管理容器的网络和存储
- 启动、停止、查看容器日志等
- 与Docker注册表进行通信,管理镜像的存储与分发
总的来说,docker守护进程监听来自Docker客户端的请求,并且通过Docker API执行这些请求,守护进程将负责容器、镜像等。Docker对象的管理,并根据请求的参数启动容器、删除容器、修改容器配置等。一般情况下,守护进程是自启动的。当然可以通过输入
sudo systemctl is-enabled docker
来检查是否为自启动,如果输出是enabled
,则表示 Docker 已设置为随系统启动而自动启动。如果是disabled
,则需要使用 sudo systemctl enable docker 来启用自动启动。此外,我们还可以随时启动Docker守护进程而不依赖来于系统自启动。 -
具体例子:
sudo systemctl start docker
Docker 引擎 API
-
具体作用:像Docker提供的RESTful接口,允许外部客户端与Docker守护进程进行通信,通过API,用户可以执行各种操作,如启动容器、构建镜像、查看容器状态等。API提供了HTTP请求的接口,支持跨平台调用。
- 向Docker守护进程发送HTTP请求,实现容器、镜像的管理
- 提供RESTful接口,允许通过变成与Docker进行交互
我们可以通过curl或者其他的HTTP客户端访问Docker引擎的API。就比如,我们可以查询当前Docker守护进程版本
curl --unix-socket /var/run/docker.sock http://localhost/version
Docker 容器
-
具体作用:容器是Docker的执行环境,而且是轻量级、独立且可执行的软件包。容器是从Docker镜像启动的,包含了运行某个应用程序所需的一切,即从操作系统库到应用程序代码。此时我们运行时的容器、其他容器和我们宿主机共享操作系统内核,但是容器与容器之间的文件系统和进程都是隔离的。
- 提供独立的运行环境,确保应用程序在不同的环境拥有一致的行为
- 容器是临时的,通常在任务完成后被销毁
我们刚刚讲过守护进程,它是我们docker架构的核心,负责很多东西,其中管理容器生命周期就是它负责的其中之一,所以容器可以运行在任何地方,因为它不依赖我们底层操作系统的配置,它所有运行时的依赖都一已经封装在镜像中了。
启动一个容器
docker run -d 镜像id
Docker 镜像
-
具体作用:镜像是容器的只读模板。每个镜像都包含了应用程序运行所需的操作系统、运行时、库、环境变量和应用代码。镜像是静态的,用户可根据镜像启动容器。
- 镜像是构建容器的基础,每个容器实例化时都会使用镜像
- 镜像是只读的,不同容器使用同一个镜像时,容器中的文件系统层时独立的,互不干扰
Docker镜像可以通过docker pull从Docker Hub或者私仓拉取,也可以通过docker build从Dockerfile构建
拉取镜像
docker pull 镜像
Docker 仓库
-
具体作用:Docker是用来存储Docker镜像的地方,分为公共仓库和私仓。我们最常用的公共仓库是Docker Hub。用户可以从Docker Hub下载镜像,当然也可以上传自己的镜像分享给别人。除了公告仓库,一些企业也可以部署自己的私仓来管理企业内部的镜像。
- 存储docker镜像
- 提供镜像的上传和下载
推送镜像到Docker Hub
docker push 镜像
Docker Compose
-
具体作用:是一个用于定义和运行多容器的工具。通过Compose,用户可以使用一个名为
docker-compose.yml
配置文件定义多个容器,并且可以通过一个命令启动这些容器。Docker Compose 主要用于开发、测试和部署多容器的应用。- 定义和运行多个容器组成的应用
- 通过YAML文件来配置应用的服务、网络和卷等
创建一个简单的docker-compose.yml文件来配置一个包含Web服务和数据库服务的应用
version:'3' service:web:image: nginxport:- "8080:80"db:image: mysqlenvironment:MYSQL_ROOT_PASSWORD: example
启动Compose定义的所有服务:
docker-compose up
Docker Swarm
-
具体作用:是docker提供集群管理和调度的工具。它允许将多个Docker主机(节点)组织成一个集群,并通过Swarm集群管理工具来调度和管理容器。Swarm可以实现容器的负载均衡、高可用性和自动扩展等功能。
- 管理多节点Docker集群
- 通过调度器管理容器的部署和扩展
初始化Swarm集群:
docker swarm init
Docker 网络
-
具体作用:允许容器之间相互通信,并于外部世界进行连接。Docker提供了许多网络模式来满足不同的需求,如bridge网络(默认)、host网络和overlay网络等。
- 管理容器间的网络通信
- 支持不同的网络模式,以适应不同场景下的需求
创建一个自定义网络并将容器连接到该网络
docker network create 网络 docker run -d --network 网络 基础镜像名
Docker 卷
-
具体作用:是一种数据持久化机制,允许数据在容器之间共享,并且独立于容器的生命周期。与容器文件系统不同,卷的内容不会随着容器的销毁而丢失,适用于数据库等需要持久存储的应用。
- 允许容器之间共享数据
- 保证数据持久化,独立于容器的生命周期
创建并挂载卷
docker volume create 我的卷 docker run -d -v 我的卷:/数据位置 基础镜像
如何使用
关于容器
我们可以理解容器是操作系统级别的虚拟化,不需要运行完整的操作系统,启动和运行更为高效。
镜像与容器的关系
镜像是容器的静态模板,包含了应用程序运行所需的所有依赖和文件,镜像是不可变的。
容器是镜像的一个运行实例,具有自己的文件系统、进程、网络等,且是动态的。容器从镜像启动,并在运行时保持不变。
关于容器有很多指令:
拉取镜像:
docker pull mysql:lastes
查看镜像:
docker images
启动容器:
- 进入交互终端并设置root密码,因为我的是mysql所以会设置密码
docker run -it mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql /bin/bash
如果想退出终端,直接使用exit
即可
- 在后台以交互模式启动一个mysql容器
docker run -itd \--name my-mysql \ # 指定容器名称-e MYSQL_ROOT_PASSWORD=my-secret-pw \ # 设置 MySQL root 密码-v /path/to/data:/var/lib/mysql \ # 挂载数据卷-p 3306:3306 \ # 映射端口mysql # 使用的镜像
-i :保持标准输入打开,也就是以交互的方式
-t:分配一个伪终端,也就是提供终端界面
-d:以后台模式运行容器
-e:设置环境变量
-v:实现数据持久化
如果想要进入后台的容器docker attach 容器id
:这将允许你与容器的标准输入、输出、标准错误进行交互
进入后台容器也可以使用docker exec -it 容器id /bin/bash
,因为这个命令会退出容器终端但不会导致容器的停止
导出容器:
docker export 容器id > 指定tar包
导入容器快照:
cat docker /tar包 |docker import -test/镜像:tag
可以通过指定的URL或者某个目录来导入
docker import http://example.com/exampleimage.tgz example/imagerepo
删除容器:
docker rm -f 容器id
清理掉所有处于终止静止状态的容器:
docker container prune
关于镜像
列出镜像列表:
docker images
会显示如下:
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu 14.04 90d5884b1ee0 5 days ago 188 MB
php 5.6 f40e9e0f10c8 9 days ago 444.8 MB
说明:
- REPOSITORY:表示镜像的仓库源
- TAG:镜像的标签
- IMAGE ID:镜像ID
- CREATED:镜像创建时间
- SIZE:镜像大小
同一个仓库源可以有多个TAG,代表这个仓库源的不同版本
所以,我们如果要使用版本为15.10的ubuntu系统镜像来运行容器时,命令如下:
docker run -t -i ubuntu:15.10 /bin/bash
说明:
- **-i:**交互式操作
- **-t:**终端
- **ubuntu:15.10:**这是指用ubuntu15.10版本镜像为基础来启动容器
- **/bin/bash:**放在镜像名后的是命令,因为上面命令希望有个交互Shell,因此用的是/bin/bash
获取一个新的镜像:
docker pull 镜像
查找镜像:
docker search 镜像名称
删除镜像:
docker rmi 镜像id
更新镜像:
docker run -t -i ubuntu:15.10 /bin/bash //先使用镜像来创建一个容器
apt-get update
apt-get upgrade -y //更新系统
exit //退出容器
提交容器副本:
docker commit -m="has update" -a="runoob" 容器id runoob/ubuntu:v2
说明:
- -m:提交的描述信息
- -a:指定镜像作者
- runoob/ubuntu:v2:指定要创建的目标镜像名
构建镜像:
关于构建镜像我们使用的是docker build
,但是它找的是dockerfile文件,所以需要创建一个dockerfile文件,其中包含一组指令来告诉Docker如何构建镜像。
大致的样子是:
FROM centos:6.7
MAINTAINER Fisher "fisher@sudops.com"
RUN /bin/echo 'root:123456' |chpasswd
RUN useradd runoob
RUN /bin/echo 'runoob:123456' |chpasswd
RUN /bin/echo -e "LANG=\"en_US.UTF-8\"" >/etc/default/local
EXPOSE 22
EXPOSE 80
CMD /usr/sbin/sshd -D
具体说明在后面…
一般会使用以下指令
docker build -t 镜像名:版本号 .
说明:
- -t:指定要创建的目标镜像名
- .:Dockerfile文件所在目录,可以指定Dockerfile的绝对路径
设置镜像标签:
docker tag 镜像id 自定义的标签名
Docker容器连接
容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过-P
或-p
参数来指定端口映射
网络端口映射:
docker run -d -P 运行的镜像名称 在容器启动后要执行的命令
docker run -d -p 5000:5000 运行的镜像名称 在容器启动后要执行的命令
说明:
- **-P:**是容器内部端口随机映射到主机的端口
- **-p:**是容器内部端口绑定到指定的主机端口 (客户端口:容器端口)
指定容器绑定的网络地址:
docker run -d -p 127.0.0.1:5001:5000 镜像名称 在容器启动后要执行的命令
上面默认都是绑定的TCP端口,如果要绑定UDP端口,可以在端口后面加上/UDP
docker run -d -p 127.0.0.1:5000:5000/udp 镜像名称 在容器启动后要执行的命令
快捷查看端口的绑定主机情况:
docker port 容器名称 容器对应端口
结果举例:
127.0.0.1:5001 对应主机的ip和端口号
Docker容器互联
端口映射并不是唯一把docker连接到另一个容器的办法,docker有一个连接系统允许将多个容器连接一起,共享连接信息。
docker连接会 创建一个父子关系,其中父容器可以看到子容器的信息
容器命名:
当我们创建一个容器时,docker会对它进行命名。另外,我们也可以使用–name标识来命名容器
docker run -d -P --name 自定义名称 镜像名称 容器启动后要执行的命令
新建网络:
docker network create -d bridge 自定义网络名称
说明:
- **-d bridge:**这里
-d
参数指定要使用的驱动程序类型,bridge
是docker默认的网络驱动 ,它会在Docker主机上创建一个单独的网络桥,允许连接到它的容器之间相互通信,并提供隔离性
连接容器:
docker run -itd --name 镜像名称 --network 网络名称 /bin/bash
连接同一个网络的容器可以通过ping的命令去查看,但如果有多个容器相互连接,更推荐使用Docker Compose
配置DNS:
在docker网络环境中配置DNS设置对于确保容器能够正确解析域名至关重要,当不特别指定DNS服务器时,Docker使用主机的DNS设置(/etc/resolv/.conf获取)为容器配置DNS。然而,如果主机使用了本地网络中的DNS服务器(如:127.0.0.1),这些地址将不会直接适用容器内,因为它们指向的是主机上的服务而不是容器内部的服务。
可以在启动容器时使用--dns
参数来指定一个或多个自定义DNS服务器:
docker run -it --rm --dns 8.8.8.8 --dns 8.8.4.4 镜像名称
docker run -it --rm -h 主机名 --dns=114.114.114 --dns-search=要搜索的域名
也可以在宿主机的/etc/docker/daemon.json文件中配置守护进程级别的DNS设置:
{"dns" : ["114.114.114.114","8.8.8.8"]
}
保存更改后,重新加载Docker守护进程以使更改失效:
sudo systemctl reload docker
设置完成后,启动容器的DNS会自动配置为114.114.114.114和8.8.8.8
查看容器的DNS是否生效可以使用以下命令,它会输出容器的DNS信息:
docker run -it --rm 镜像名 cat etc/resolv.conf
Dockerfile
dockerfile是一个文本文件,包含了构建Docker镜像的所有指令
使用docker定制镜像
以nginx为例:
FROM nginx
RUN echo '这是一个本地构建的nginx镜像'> /usr/share/nginx/html/index.html
说明:
-
**FROM:**定制的镜像都是基于FROM的镜像,这里的nginx就是定制需要的基础镜像
-
**RUN:**用于执行后面跟着的命令行命令
shell格式:
RUN 命令行命令 #命令行即在终端操作的shell命令
exec格式:
RUN ["可执行文件","参数1","参数2"] #RUN ["./test.php","dev","offline"] 等价于 ./test.php dev offline
在docker中,镜像是由一系列只读层组成,每一层代表对文件系统的修改,这些层是基于Dockerfile中的每一条指令构建的。每当Docker处理Dockerfile中的一条指令时,它会在前一层的基础上创建一个新的层来表示该指令的效果。如果Dockerfile中包含过多不必要的或者不优化的指令,可能会导致生长很多小的、不必要的层,这会使最终生成的镜像变得臃肿
如:
FROM centos RUN yum -y install wget RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" RUN tar -xvf redis.tar.gz
会创建3层镜像
FROM centos RUN yum -y install wget \&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \&& tar -xvf redis.tar.gz
以上使用
&&
符号连接命令,这样执行后,只会创建1层镜像上下文路径:
构建镜像的指令中docker build -t 镜像名 .
有个.
,这就是上下文路径
上下文路径是指docker在构建镜像,有时候想要使用到本机的文件(如复制),docker build命令得知这个路径后,会将路径下的所有内容打包。
- **COPY:**复制指令,从上下文目录中复制文件或者目录到容器指定路径
COPY [--chown=<user>:<group>] <源路径1>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",..."<目标路径>"]
[–chown=:]:可选参数,用户改变复制到容器内文件的拥有者和属组。
**<源路径>:**源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足Go的filepath.Match。如:
COPY hom* /mydir/ #所有以hom开头的文件或目录
COPY hom?.txt /mydir/ #?表示任意单个字符 所有匹配到hom?.txt复制到/mydir路径
**<目标路径>:**容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。
-
**ADD:**ADD指令和COPY使用格式类似,将文件、目录或远程URL复制到镜像中
ADD <源文件>...<目标路径>
ADD ["<源路径>",..."<目标路径>"]
区别:
- 功能范围:
- ADD:可以将文件、目录或远程URL复制到镜像中。
- COPY:仅能将文件或目录复制到镜像。
- 使用场景:
- ADD:适应于需要从远程URL下载文件并将其添加到镜像中的情况。如可以从互联网下载一个压缩包并解压到镜像中。
- COPY:适应于本地文件和目录的复制,不支持远程URL的处理。
- 自动解压功能:
- ADD:如果源文件是一个可识别的压缩格式(如.tar,.zip),Docker会自动解压该文件到目标路径。
- COPY:不会自动解压文件,只是简单地复制文件或目录。
- 安全性和最佳实践:
- COPY:通常推荐使用COPY而不是ADD。除非确实需要从远程URL获取资源。因为COPY更加透明,更容易理解其行为,且没有自动解压等额外操作,减少了 潜在的安全风险和复杂性。
-
**CMD:**类似与RUN指令,用于运行程序,但二者运行的时间点不同
- CMD是在docker run时运行,提供容器启动时默认要执行的参数或命令,如RUN不同,CMD并不在镜像构建阶段执行,而是在基于该镜像启动容器时执行。
- RUN是在镜像构建阶段执行命令。它主要用于安装软件包、修改文件、下载依赖等操作。
作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD指令指定的程序可被docker run命令行参数中指定要运行的程序所覆盖,即如果存在多个CMD指令,仅最后一个生效。
CMD <shell 命令> CMD ["<可执行文件或命令>","<param1>","<param2>",...] CMD ["echo", "Hello, World!"] #推荐使用
-
**ENTRYPOINT:**类似与CMD指令,但其不会被docker run的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给ENTRYPOINT指定的程序。
但如果运行docker run时使用了 -entrypoint选项,将覆盖ENTRYPOINT指令指定的程序。
优势:在执行docker run时可以指定ENTRYPOINT运行所需的参数。但是如果Dockerfile中存在ENTRYPOINT指令,仅最后一个生效。
ENTRYPOINT ["<executeable>","<param1>","<param2>",...]
可以搭配CMD命令使用:一般是变参才会使用CMD,这里的CMD等于是在给ENTRYPOINT传参
FROM nginx ENTRYPOINT ["nginx","-c"] #定参 CMD ["/etc/nginx/nginx.conf"] #变参
- 若不传参运行:
docker run nginx:test
容器内会默认运行以下命令,启动主进程
nginx -c /etc/nginx/nginx.conf
- 传参运行:
docker run nginx:test -c /etc/nginx/new.conf
容器内会默认运行一下命令,启动主进程(/etc/nginx/new.conf 容器内已有的文件)
nginx -c /etc/nginx/new.conf
-
**ENV:**设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量
ENV <key> <value> ENV <key1>=<value1> <key2>=<value2>
以下示例设置NODE_VERSION = 7.2.0,在后续的指令中可以通过$NODE_VERSION引用:
ENV NODE_VERSION 7.2.0 RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"
-
**ARG:**构建参数,与ENV作用一致,不过作用域不一样。ARG设置的环境变量仅对Dockerfile内有效,也就是说只有docker build的过程中有效,构建好的镜像内不存在此环境环境变量。
ARG <参数名>[=<默认值>]
-
**VOLUME:**定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。可以避免重要的数据,因容器重启而丢失,还可以避免容器不断变大
VOLUME ["<路径1>","<路径2>"...] #如 VOLUME["/data/db","/data/config"]将以上两个路径设置为数据卷,表示表示 /data/db 和 /data/config 目录下的任何修改都将被存储在一个数据卷中 #也可以分开写 VOLUME ["<路径1>"] VOLUME ["<路径2>"]
虽然VOLUME指令声明了哪些路径应该作为数据卷,但它并不会自动将这些卷与主机上的特定目录关联起来。实际的目录挂载是在运行容器时通过-v或–mount参数来完成的
docker run -v /host/path:/container/path 镜像
以上可以确保
/host/path
目录的内容会被挂载到容器内的/container/path
路径上 -
**EXPOSE:**仅仅是声明端口。可用于帮助理解镜像服务的守护端口,以方便配置映射。在运行时使用随机端口映射时,也就是docker run -P时,会自动随机映射
EXPOSE
的端口。EXPOSE <端口1> [<端口2>...]
-
**WORKDIR:**指定工作目录,用WORKDIR指定的工作目录,会构建镜像的每一层中都存在。以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR会帮忙建立目录。
注:是否需要Dockerfile取决于具体的需求场景。如果只是简单地运行某个已有服务,直接使用已有的镜像是最简单的做法;但如果涉及到环境定制、应用部署等需求,则编写Dockerfile是必要的步骤
示例:
#基于nginx最新LTS版制作
FROM adoptopenjdk/openjdk8:jre#拷贝HTML文件免去实时挂载
COPY FileConver-0.0.1-SNAPSHOT.jar /autoduty/
#将外部的配置文件复制进去
COPY application.properties /autoduty/#启动时执行命令
CMD ["java", "-jar", "/autoduty/FileConver-0.0.1-SNAPSHOT.jar","--spring.config.location=/autoduty/application.properties"]
Docker Compose
docker compose
是一个可以定义多容器的应用程序工具,而我们通常使用YML文件去配置应用程序所需要的所有服务,然后使用一个命令,就可以从YML文件配置中创建并启动所有服务。
- 使用dockerfile定义应用程序的环境
- 使用docker-compose.yml定义构成应用程序的服务,这样它们可以在隔离环境中仪器运行
- 最后在命令行页面执行 docker-compose up命令来执行并运行整个应用程序
示例:
#指定docker compose版本
version: '3.1'
# 定义要启动的服务列表,每个服务对应一个单独的Docker容器
services:test: #服务名 image: 'test:v1' #指定用于启动该服务的Docker镜像及其标签restart: always #容器自动启动volumes: #定义主机文件系统与容器内部之间的卷挂载点,允许在两者之间共享数据- '/root/Desktop/test/autoduty/application.properties:/autoduty/application.properties' ports: #定义了主机的5000端口映射到容器的80端口- 8089:8080
但是一般我是先将dockerfile写好,通过docker bulid
先构建镜像,然后再docker-compose中指明镜像名 也就是image: 'test:v1'
关于volumes
是为了保证数据共享,:
前的是外部文件,必须要指定正确,不然会找不到你的文件,后面的是容器内的,这个可以自己自定义写,如果没有会自己创建。
ports
可以理解为你通过docker run 运行时,后面有个-p,用于主机与容器之间的端口映射
- 以上示例的yaml文件表示我启动一个名为wxvoice服务(自定义的名字),镜像为test:v1(已经构建好了的),将本地文件
/root/Desktop/test/autoduty/application.properties
挂载到容器中/autoduty/application.properties
。当客户端的端口为8089时映射到容器中的8080端口