容器技术之docker
一、什么是Docker
- Docker是一个用于构建build 运行run 传送share 应用程序的平台.
- Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可抑制的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。容器完全使用沙盒机制,相互之间不会存在任何接口。几乎没有性能开销,可以很容易的在机器和数据中心运行。最重要的是,他们不依赖于任何语言、框架或者包装系统。
二、为什么会出现Docker
Docker 的出现是为了解决软件开发、部署和运维中的一系列核心问题,其背景和原因可以总结为以下几点:
2.1 环境一致性问题
- 传统痛点:开发、测试、生产环境不一致(如操作系统、依赖库版本差异)导致“在我机器上能跑,线上却失败”的问题。
- Docker 的解决:通过容器将应用及其所有依赖(代码、运行时、系统工具等)打包成一个标准化、轻量级的镜像,实现“一次构建,处处运行”。
2.2 虚拟化技术的局限性
- 传统虚拟机(VM):每个 VM 需要独立的操作系统内核,资源占用高(GB 级)、启动慢(分钟级),且性能有损耗。
- Docker 的优势:容器共享主机 OS 内核,资源占用更少(MB 级)、启动更快(秒级),同时保持进程隔离,效率接近原生。
2.3 微服务架构的兴起
- 需求变化:微服务提倡将应用拆分为多个小型服务,每个服务需要独立部署、扩展和管理。
- Docker 的适配:轻量级容器天然适合微服务,每个服务可打包为独立容器,灵活组合、快速伸缩。
三、重要概念
3.1 什么是镜像
Docker 镜像类似于虚拟机镜像,可以将它理解为一个只读的模板,例如,一个镜像可以包含一个基本的操作系统环境(例如:CentOS7),可以把它称为一个 CentOS7 镜像
3.2 什么是容器
1.Docker 容器类似于一个轻量级的沙箱, Docker利用容器来运行和隔离应用
2.容器是从镜像创建的应用运行实例 它可以启动、开始、停止 删除,而这些容器都是彼此相互隔离、互不可见的
3.可以把容器看作一个简易版的 Linux 系统环境(包括 root 用户权限、进程空间、用户空间和网络空间等)以及运行在其中的应用程序打包而成的盒子
3.3 总结:
- 镜像和容易可以理解为类和实例,镜像就是一个只读的模板,容器就是这个模板的实例;
- docker本质就是宿主机的一个进程,docker是通过namespace实现资源隔离,通过cgroup实现资源限制,通过写时复制技术(copy-on-write)实现了高效的文件操作(类似虚拟机的磁盘比如分配500g并不是实际占用物理磁盘500g)
3.2 Docker仓库
Docker仓库是用来存储Docker镜像的地方,最流行的docker仓库就是dockerhup,可以在这里上传和下载镜像
四、安装Docker(Ubuntu系统下)
1.卸载老的版本
sudo apt-get remove docker docker-engine docker.io containerd runc
2.更新apt包索引
sudo apt-get update
3.安装必要工具包
sudo apt-get install \apt-transport-https \ca-certificates \curl \doc-gnupg-agent \software-properties-common
4.添加Docker GPG秘钥
a.默认使用国外源,非常非常非常慢!
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
b. 推荐使用国内源,顺畅!
sudo curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
5.配置仓库源
a. 默认使用国外源,非常非常非常慢!
sudo add-apt-repository \"deb [arch=amd64] https://download.docker.com/linux/ubuntu \$(lsb_release -cs) \stable"
b. 推荐使用国内源,顺畅!
sudo add-apt-repository \"deb [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu \$(lsb_release -cs) \stable"
6.安装Docker Engine
a.更新apt包索引
sudo apt-get update
b.安装docker
sudo apt-get install docker-ce docker-ce-cli containerd.io
7.启动Docker
//启动 Docker
sudo systemctl start docker
//设置开机自启(可选)
sudo systemctl enable docker
//检查 Docker 是否运行
sudo systemctl status docker
8.验证安装是否成功
在docker启动的前提下,在命令行输入以下指令:
docker run hello-world
结果:
五、docker的结构
架构图:
Docker 在运行时分为 Docker 引擎(服务端守护进程) 和 客户端工具,我们日常使用各种 docker 命令,其实就是在使用 客户端工具 与 Docker 引擎 进行交互。
Docker 是一个客户端-服务器(C/S)架构程序。Docker 客户端只需要向 Docker 服务器或者守护进程发出请求,服务器或者守护进程将完成所有工作并返回结果。可以在同一台宿主机上运行 Docker 守护进程和客户端,也可以从本地的 Docker 客户端连接到运行在另一台宿主机上的远程 Docker 守护进程(未验证)。
docker client发送容器管理请求后,由docker daemon接受并处理请求,当docker client 接收到返回的请求响应并简单处理后,docker client 一次完整的生命周期就结束了,当需要继续发送容器管理请求时,用户必须再次通过docker可执行文件创建docker client。
docker daemon 是docker架构中一个常驻在后台的系统进程,功能是:接收处理docker client发送的请求。该守护进程在后台启动一个server,server负载接受docker client发送的请求;接受请求后,server通过路由与分发调度,找到相应的handler来执行请求。
六、镜像分层
Docker 支持通过扩展现有镜像,创建新的镜像。
从上图中可以看到,新镜像是从 base 镜像一层一层叠加生成的。每安装一个软件,就在现有镜像的基础上增加一层。
镜像分层最大的一个好处就是共享资源。比如说有多个镜像都从相同的 base 镜像构建而来,那么 Docker Host 只需在磁盘上保存一份 base 镜像;同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。而且镜像的每一层都可以被共享。
如果多个容器共享一份基础镜像,当某个容器修改了基础镜像的内容,比如 /etc 下的文件,这时其他容器的 /etc 是不会被修改的,修改只会被限制在单个容器内。这就是容器 Copy-on-Write 特性。
七、Docker 常用命令
7.1 镜像命令
docker images #查看所有本地主机的镜像
docker search 镜像名 #搜索镜像
docker pull 镜像名 [标签] #下载镜像(如果不写tag,默认是latest)
docker rmi 镜像名 [标签] #删除镜像 docker rmi -f $(docker images -aq) 删除全部镜像
docker tag 镜像名:版本 新镜像名:版本 #复制镜像并且修改名称
docker commit -a "xxx" -c "xxx" 镜像ID 名字:版本 #提交镜像
-a :提交的镜像作者;
-c :使用Dockerfile指令来创建镜像;
-m :提交时的说明文字;docker save -o /xxx/xxx.tar #保存一个镜像为一个tar包
# sudo docker save -o nginx.tar nginx:latestdocker load -i /xxx/xxx.tar #导入镜像
# sudo docker load -i nginx.tar
7.2 容器命令
docker run [可选参数] image 命令 #启动容器(无镜像会先下载镜像)
#参数说明
--name = "Name" 容器名字
-c 后面跟待完成的命令
-d 以后台方式运行并且返回ID,启动守护进程式容器
-i 使用交互方式运行容器,通常与t同时使用
-t 为容器重新分配一个伪输入终端。也即启动交互式容器
-p 指定容器端口 -p 容器端口:物理机端口 映射端口
-P 随机指定端口
-v 给容器挂载存储卷docker build #创建镜像 -f:指定dockerfile文件路径 -t:镜像名字以及标签
docker logs 容器实例的ID #查看容器日志
docker rename 旧名字 新名字 # 给容器重新命名
docker top 容器实例的ID #查看容器内进程
docker ps -a #列出所有容器(不加-a就是在运行的)
docker rm 容器实例的ID #删除容器(正在运行容器不能删除,除非加-f选项)
docker kill 容器实例的ID #杀掉容器
docker history 容器实例的ID #查看docker镜像的变更历史
docker start 容器实例的ID #启动容器
docker restart 容器实例的ID #重启容器
docker stop 容器实例的ID #停止正在运行的容器
docker attach /docker exec 容器实例的ID #同为进入容器命令,不同的是attach连接终止会让容器退出后台运行,而exec不会。并且,docker attach是进入正在执行的终端,不会情动新的进程,而docker exec则会开启一个新的终端,可以在里面操作。
docker image inspect 容器名称:容器标签 #查看容器内源数据
docker cp 容器id:容器内路径 目的主机路径 #从容器内拷贝文件到主机(常用)或者从主机拷贝到容器(一般用挂载)
exit #直接退出容器
crlt + P + Q #退出容器但是不终止运行
启动容器的方法总结:
第一种:交互方式创建容器,退出后容器关闭。
docker run -it 镜像名称:标签 /bin/bash
第二种:守护进程方式创建容器。
docker run -id 镜像名称:标签
通过这种方式创建的容器,我们不会直接进入到容器界面,而是在后台运行了容器,
如果我们需要进去,则还需要一个命令。
docker exec -it 镜像名称:标签 /bin/bash
通过这种方式运行的容器,就不会自动退出了。
八、数据卷
常用命令:
示例
启动容器:
sudo docker run -d --name nginx_ty -p 80:80 -v html:/usr/share/nginx/html nginx
查看卷是否成功(可以看到出现了html):
查看卷的详细信息:
sudo docker volume inspect html
其中"Mountpoint": “/var/lib/docker/volumes/html/_data”,对应的就是宿主机映射目录。
九、本地目录挂载
如果没有mysql镜像,可以先拉去,
sudo docker pull mysql
docker run -d \
--name mysql \
-p 3306:3306 \
-e TZ=Asia/Shanghai \
-e MYSQL_ROOT_PASSWORD=123 \
-v /home/descfly/桌面/mysql/data:/var/lib/mysql \
-v /home/descfly/桌面/mysql/init:/docker-entrypoint-initdb.d \
-v /home/descfly/桌面/mysql/conf:/etc/mysql/conf.d \
mysql
执行上面的命令后,然后去刚才创建的文件夹中查看:
可以看到挂载成功了!此时在宿主机挂载的目录操作,比如修改文件,增加文件,对应的容器也就有了同样的操作!
备注:如果把容器删除,映射的目录是不会删除的,如果往刚才的目录中加入一个文件,再把容器删除,如果下次,映射的目录一样,依然会被挂载。这就方便我们保存数据,而不会因为容器删了数据就丢失了。
十、如何制作镜像
镜像就是包含了应用程序、程序运行的系统函数库,运行配置等文件的文件包,构建镜像的过程其实就是把上述文件打包的过程。
10.1 Dockfile文件
Dockerfile是一个创建镜像所有命令的文本文件,包含了一条条指令和说明, 每条指令构建一层,,通过docker build命令,根据Dockerfile的内容构建镜像,因此每一条指令的内容, 就是描述该层如何构建。有了Dockefile,,就可以制定自己的docker镜像规则,只需要在Dockerfile上添加或者修改指令,,就可生成docker 镜像。
详细指令如下:
Dockerfile 指令选项:FROM #基础镜像 。 (centos)
MAINTAINER #镜像的作者和邮箱。(已被弃用,结尾介绍代替词)
RUN #镜像构建的时候需要执行的命令。
CMD #类似于 RUN 指令,用于运行程序(只有最后一个会生效,可被替代)
EXPOSE #对外开放的端口。
ENV #设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
ADD # 步骤:tomcat镜像,这个tomcat压缩包。添加内容。
COPY #复制指令,将文件拷贝到镜像中。
VOLUME #设置卷,挂载的主机目录。
USER #用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
WORKDIR #工作目录(类似CD命令)。
ENTRYPOINT #类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,会追加命令。
ONBUILD #当构建一个被继承Dokcerfile,就会运行ONBUILD的指令。出发执行。注意:CMD类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:
CMD 在docker run 时运行。
RUN 是在 docker build。
作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。
CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。
如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。LABEL(MAINTALNER已经被弃用了,目前是使用LABEL代替)
LABEL 指令用来给镜像添加一些元数据(metadata),以键值对的形式,语法格式如下:
LABEL <key>=<value> <key>=<value> <key>=<value> ...
比如我们可以添加镜像的作者:
LABEL org.opencontainers.image.authors="runoob"
10.2 制作hellodocker镜像
1.创建项目文件结构
mkdir cpp-docker-demo
cd cpp-docker-demo
2.编写c++程序
hello.cpp
#include <iostream>
int main()
{std::cout << "Hello Docker!" << std::endl;return 0;
}
3.创建Dockerfile文件
Dockerfile:
#使用官方 GCC 镜像作为基础
FROM gcc:latest#设置工作目录
WORKDIR /usr/src/app#将当前目录下的文件复制到容器的工作目录
COPY . .#编译程序
RUN g++ hello.cpp -o hello.exe #设置容器启动时运行的命令
CMD ["./hello.exe"]
4.构建Docker镜像
sudo docker build -t cpp-hello .
这一步如果首次构建会耗时较长
5.运行刚才创建的镜像,让它成为一个容器
–rm 运行完退出容器
十一、网络
安装docker的时候,就会在虚拟机创建一张虚拟网卡:docker0;容器之间能访问就是它们有相同的网关,并且会给这个网卡创建一个虚拟的网桥。
按理来说新建的容器之间已经能够互通了,其实是不对的,因为容器的IP是docker分配的,如果服务重启,IP地址可能就变了,那如何解决呢???
解决办法:自定义网络
注意:如果容器名能访问,那IP变不变就无所谓了,从而解决了问题!
如果创建了自定义网络,就会形成一个新的网桥,会形成一个新的网段,加入该网段的容器就能相互访问。
创建一个网络:tyNet
sudo docker network create tyNet
将容器加入tyNet
sudo docker network connect tyNet mysql
也可以再创建的时候直接加入网络,例如
sudo docker run -d --name nginx_ty -p 8080:8080 --network tyNet nginx
此时进入容器,
sudo docker exec -it nginx_ty bash
然后进行ping命令:ping mysql
总结:容器加入自定义网络就可以通过容器名互相访问,但是默认网络不可以通过容器名进行互相访问。
十二、DockerCompose
多容器示例:
那如何使用该文件?
参考:
docker的架构及工作原理(详解)
太全了|万字详解Docker架构原理、功能及使用