—— Notes from WAX through KuangShen
准确来说,这是一篇学习笔记!!!
一款产品:开发—上线 两套环境!应用环境如何铜鼓?
开发 – 运维。避免“在我的电脑米问题呀!!!”
环境配置十分麻烦,没一个机器都要部署环境(集群Redis、ES…)!费时费力。
发布一个项目(jar + (Redis、Mysql、jdk、ES))能否带上安装环境进行打包发布。
Windows开发,最后发布到Linux:
传统:开发项目,运维来做。
现在:开发打包部署上线,一套流程做完。
例:
java – apk – 发布(应用商店) – 张三使用apk – 安装即可使用!
java – jar(环境) – 打包项目带上环境(镜像)-- (Docker仓库:商店)-- 下载发布的镜像 – 直接运行即可!
Docker给出以上问题的解决方案!
Docker的思想来自于集装箱!
隔离:Docker核心思想,打包装箱!每个箱子是互相隔离的
Docker通过隔离机制,可以将服务器利用到极致!
2010年,几个搞IT的年轻人,就在没美国成立了一家公司dotCloud
做一些pass的云计算服务,LXC有关的容器技术!
他们将自己的技术(容器话技术)命名 就是Docker!
Docker刚刚诞生的时候,没有引起行业注意!公司举步维艰,于是决定开源!!!
2013,Docker开源!
Docker越来越多的人发现Docker的有点!火了,每个月都会更新一个版本!
2014年4月9日,Docker1.0发布!
Docker为什么这么火?极其轻巧
在容器技术出来之前,我们都是使用虚拟技术!
虚拟机:在Windows中装一个Vmware,通过这个软件可以虚拟化出来一个或多个电脑,但是十分笨重。
虚拟机也是属于虚拟化技术,Docker容器技术,也是一种虚拟化技术
vm:linux centos原生镜像(一个电脑) 隔离,需要开启多个虚拟机! 几个G大小
docker:隔离,镜像(最核心的环境4m + jdk + mysql)十分轻巧,运行镜像就可以了,几 M Kb 秒级启动
到现在,所有开发人员都必须会的工具 Docker。
聊聊docker
Docker是基于 Go 语言开发的,开源项目!
官网:https://www.docker.com
文档:https://docs.docker.com 超详细
仓库地址:https://hub.docker.com
虚拟机技术缺点:
1、资源占用多
2、冗余步骤多
3、启动慢
Docker和虚拟技术的不同:
统虚拟机,虚拟出一切硬件,运行一个完整的操作系统,然后在这个系统上安装和运行软件
容器内的应用直接运行再宿主机的内核,容器是没有自己的内核的,也没有虚拟我们的硬件,所以轻便
每个容器内是相互隔离的,每个容器内都有一个属于自己的文件系统,互不影响
DevOps(开发/运维)
应用更快速的交付和部署
传统方式:一堆帮助文档,安装程序
Docker:打包镜像发布测试,意见运行
更快捷的升级和扩缩容
使用Docker以后,我们不熟应用就类似搭积木简单
项目打包为一个镜像,扩容 服务器A 服务器B
更简单的系统运维
更高效的计算资源利用:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cXdU4l9R-1691285233022)(C:\Users\wax\AppData\Roaming\Typora\typora-user-images\image-20210424153809962.png)]
镜像(image):
Docker镜像好比一个模板,可以通过这个模板来创建容器服务。
通过这个镜像可以创建多个容器(最终服务运行或项目运行就是在容器中的)
容器(container):
Docker利用容器技术,独立运行一个或者一组应用,通过镜像来创建。
虽然不是很准确,但是可以粗略的连接容器就是一个简单的LInux系统
仓库(repository):
仓库就是存放镜像的地方
仓库分为公有仓库和私有仓库
Docker Hub
阿里云…都有容器服务器
安装环境
环境查看
#系统版本是3.10以上的
[root@sa ~]# uname -r
3.10.0-957.el7.x86_64
[root@sa ~]#
#系统版本
[root@sa ~]# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"
安装docker
帮助文档:https://docs.docker.com/engine/install/
# 1、删除旧的版本
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
# 2、依赖包
sudo yum install -y yum-utils
# 3、设置镜像的仓库,当然也可以设置阿里云的
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo 官方
sudo yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 阿里云
#更新yum软件包索引
yum makecache fast
# 4、安装docekr -ce表示社区版 -ee表示企业版
yum install docker-ce docker-ce-cli containerd.io
# Ubuntu 安装
sudo apt install docker.io
# 5、运行docker
sudo systemctl start docker
# 6、查看版本信息,有则是安装成功
docker version
# 7、查看启动是否成功
docker run hello-world # 会打印“Hello from Docker!” 则成功
卸载docker
# 1、卸载依赖
sudo yum remove docker-ce docker-ce-cli containerd.io
# 2、删除资源
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
此处就不画图了:
docker run 开始,docker就会在本地查找镜像,
有,则直接运行该镜像。没有,就会在docker Hub(官方镜像仓库)上去下载,下载到本地后直接运行,没有下载到则返回错误
Docker是一个 Client - Server结构的系统,Docker的守护进程运行在主机上。通过Socket从客户端访问!
DockerServer 接收到Docekr Client的指令执行!
Docker 为什么比 VM 快
所以说,新建一容器的时候,Docker不需要像虚拟机一样重新加载一个操作系统内核,避免引导。虚拟机是加载Guest OS,分钟级别,而Docker是利用宿主机的操作系统,省略了这个复杂的过程,秒级启动。
当然,安全性方面,肯定还是VM虚拟机更安全,毕竟是一个完整的虚拟系统。
docker version # 显示docker版本信息
docker info # 显示docker的系统信息,包括镜像和容器的数量
docekr --help # 帮助
帮助文档 https://docs.docker.com/reference/
Docker images 查看所有本地镜像
[root@sa ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest d1165f221234 2 months ago 13.3kB
vue-docker-demo-image latest 4835e3101950 8 months ago 135MB
docker-demo-image latest 831b48a0447e 8 months ago 133MB
# 含义
REPOSITORY 镜像的仓库源
TAG 镜像的标签
IMAGE ID 镜像的id
REPOSITORY 镜像创建时间
SIZE 镜像大小
# 可选项
-a, --all Show all images (default hides intermediate images) # 显示所有镜像
--digests Show digests # 显示摘要
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print images using a Go template
--no-trunc Don't truncate output
-q, --quiet Only show numeric IDs # 只显示id
docekr search 搜索镜像
[root@sa ~]# docker search mysql
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 10876 [OK]
mariadb MariaDB Server is a high performing open sou… 4102 [OK]
mysql/mysql-server Optimized MySQL Server Docker images. Create… 808 [OK]
...
[root@sa ~]#
# 可选项,通过收藏来过滤
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print search using a Go template
--limit int Max number of search results (default 25)
--no-trunc Don't truncate output
[root@sa ~]# docker search mysql --filter=STARS=3000
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
mysql MySQL is a widely used, open-source relation… 10876 [OK]
mariadb MariaDB Server is a high performing open sou… 4102 [OK]
[root@sa ~]#
**docker pull ** 下载镜像
# 下载镜像 docker pull 镜像名[:tag]
[root@sa ~]# docker pull mysql
Using default tag: latest # 如果没有指定tag,默认就是latest
latest: Pulling from library/mysql
69692152171a: Pull complete # 分层下载,docker image的核心--联合文件系统
1651b0be3df3: Pull complete
951da7386bc8: Pull complete
0f86c95aa242: Pull complete
37ba2d8bd4fe: Pull complete
6d278bb05e94: Pull complete
497efbd93a3e: Pull complete
f7fddf10c2c2: Pull complete
16415d159dfb: Pull complete
0e530ffc6b73: Pull complete
b0a4a1a77178: Pull complete
cd90f92aa9ef: Pull complete
Digest: sha256:d50098d7fcb25b1fcb24e2d3247cae3fc55815d64fec640dc395840f8fa80969 # 签名
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest # 真实地址
[root@sa ~]#
# 以下两个命令等价
docker pull mysql
docker pull docekr.io/library/mysql:latest
docker images 查看镜像
[root@sa ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql latest c0cdc95609f1 3 days ago 556MB
hello-world latest d1165f221234 2 months ago 13.3kB
vue-docker-demo-image latest 4835e3101950 8 months ago 135MB
[root@sa ~]#
docker rmi 删除镜像
# docker rmi 容器id -f 强制删除(运行中的)
# docker rmi 容器id 容器id 容器id 删除多个容器
[root@sa ~]# docker rmi d11
Untagged: hello-world:latest
Untagged: hello-world@sha256:5122f6204b6a3596e048758cabba3c46b1c937a46b5be6225b835d091b90e46c
Deleted: sha256:d1165f2212346b2bab48cb01c1e39ee8ad1be46b87873d9ca7a4e434980a7726
Deleted: sha256:f22b99068db93900abe17f7f5e09ec775c2826ecfe9db961fea68293744144bd
[root@sa ~]#
# 批量删除
# -f 强制删除,-aq 仅显示所有的镜像id
[root@sa ~]# docker rmi -f $(docker images -aq)
有了镜像才可以创建容器
以centOS镜像为例:
docker pull centos
新建容器并启动
docekr run [可选参数] image
# 参数说明
--name="name" # 容器名称 name01 name02,用来区分容器
-d # 后台方式运行
-it # 使用交互方式运行,进入容器查看内容
-p # 指定容器的端口
-p ip:主机端口:容器端口 映射
-p 主机端口:容器端口(常用)
-p 容器端口
-P(大写p) # 随机指定端口
# 启动并进入容器,你会发现,现在的用户名就是容器的id
[root@sa ~]#
[root@sa ~]# docker run -it centos /bin/bash
[root@74457211455c /]#
[root@74457211455c /]#
# 退出就是exit
...
列出所有运行的容器
docker ps # 列出正在运行的容器
-a # 列出所有容器,包含已经停止的
-n=? # 列出最近创建的容器,带数字
-q # 只列出容器的id
# 列出所有容器,包含已经停止的
[root@sa ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
74457211455c centos "/bin/bash" 2 minutes ago Up 2 minutes happy_wilson
退出容器
exit # 直接停止容器并退出
Ctrl +p +q # 容器不停止退出
删除容器
docker rm # 容器id
-f # 强制删除正在运行的容器
docker rm -f $(docker ps -aq) # 删除所有的容器
docker ps -aq |xargs docker rm # 也可以删除所有容器
启动容器
docker start 容器id # 启动容器
docker restart 容器id # 重启容器
docker stop 容器id # 停止当前正在运行的容器
docker kill 容器id # 轻质停止当前容器
后台启动
# 命令 docker run -d 镜像名
[root@sa ~]# docker run -d centos
26e18764513a7ebf03f73dda143d5b1bb73054cbf74bae10d8f22ba16d4c4ea9
[root@sa ~]#
# 会产生的问题:ps发现centos 直接停了
# 原因:docke容器使用后台运行,就必须要有一个前台进程,docker发现没有应用,就会自动停止
查看日志
docker logs -tf --tail
# 显示日志
-tf # 显示日志 -t时间戳 -f Follow log output(不断输出日志)
--tail number # 要显示的日志条数,后接数字
查看容器中的进程信息
# 命令 docker top 容器id
[root@sa ~]# docker top 8b
UID PID PPID C STIME TTY TIME CMD
root 21913 21895 0 04:28 pts/0 00:00:00 /bin/bash
[root@sa ~]#
查看镜像的元数据
# 命令 docker inspect 容器id
[root@sa ~]# docker inspect 8b
[
{
"Id": "8bdbd34f37a64afa4e80eaafdaa517bd98a8ed1b3be178b2fb386865d4df7f05",
"Created": "2021-05-18T20:28:55.704722287Z",
"Path": "/bin/bash",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 21913,
"ExitCode": 0,
"Error": "",
"StartedAt": "2021-05-18T20:28:56.270071675Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:300e315adb2f96afe5f0b2780b87f28ae95231fe3bdd1e16b9ba606307728f55",
"ResolvConfPath": "/var/lib/docker/containers/8bdbd34f37a64afa4e80eaafdaa517bd98a8ed1b3be178b2fb386865d4df7f05/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/8bdbd34f37a64afa4e80eaafdaa517bd98a8ed1b3be178b2fb386865d4df7f05/hostname",
"HostsPath": "/var/lib/docker/containers/8bdbd34f37a64afa4e80eaafdaa517bd98a8ed1b3be178b2fb386865d4df7f05/hosts",
"LogPath": "/var/lib/docker/containers/8bdbd34f37a64afa4e80eaafdaa517bd98a8ed1b3be178b2fb386865d4df7f05/8bdbd34f37a64afa4e80eaafdaa517bd98a8ed1b3be178b2fb386865d4df7f05-json.log",
"Name": "/interesting_black",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "",
"ExecIDs": null,
"HostConfig": {
"Binds": null,
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "no",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": null,
"CapDrop": null,
"Capabilities": null,
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"KernelMemory": 0,
"KernelMemoryTCP": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": false,
"PidsLimit": null,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/1664d1df131ceec26e30d5f1288e4a76104ba149a4e2ed91df49cbae1d5b5c95-init/diff:/var/lib/docker/overlay2/85c0301341e0be53f9854cc3148968bb232564ca6cac942efebc0b9328ec192c/diff",
"MergedDir": "/var/lib/docker/overlay2/1664d1df131ceec26e30d5f1288e4a76104ba149a4e2ed91df49cbae1d5b5c95/merged",
"UpperDir": "/var/lib/docker/overlay2/1664d1df131ceec26e30d5f1288e4a76104ba149a4e2ed91df49cbae1d5b5c95/diff",
"WorkDir": "/var/lib/docker/overlay2/1664d1df131ceec26e30d5f1288e4a76104ba149a4e2ed91df49cbae1d5b5c95/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "8bdbd34f37a6",
"Domainname": "",
"User": "",
"AttachStdin": true,
"AttachStdout": true,
"AttachStderr": true,
"Tty": true,
"OpenStdin": true,
"StdinOnce": true,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/bash"
],
"Image": "centos",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": {
"org.label-schema.build-date": "20201204",
"org.label-schema.license": "GPLv2",
"org.label-schema.name": "CentOS Base Image",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vendor": "CentOS"
}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "f90c37e119e8231aee5aed42652c64393959cd87f061092347b98b21a5764f8e",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/docker/netns/f90c37e119e8",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "d3f5369c06fc9c93af1b5bfbc45617ee24b7900e88a33ac66793b80712912f88",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:02",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "d29efb6707572fecd8ccaadfb2f011525f39fa0a9fb0a7732be8af61baf34003",
"EndpointID": "d3f5369c06fc9c93af1b5bfbc45617ee24b7900e88a33ac66793b80712912f88",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
}
}
]
[root@sa ~]#
进入正在运行的容器
# 进入正在运行的容器
# 方式一:
docker exec -it 容器id bashShell
# 方式二:
docker attch 容器id
# docker exec 进入容器后开启一个新的终端
# docker attch 进入容器正在执行的终端,不会开启新的进程
容器和主机之间的拷贝
# 从容器内拷贝文件到主机
docker cp 容器id:/路径 /主机路径 # 建议使用绝对路径
# 进入容器
[root@sa ~]# docker exec -it f2 /bin/sh
sh-4.4#
sh-4.4# touch 123.txt
sh-4.4# ls
123.txt ks-script-esd4my7v ks-script-eusq_sc5
sh-4.4# exit
[root@sa ~]#
# 将容器内的文件拷贝至主机
[root@sa ~]# docker cp f2:/tmp/123.txt ./
[root@sa ~]#
[root@sa ~]# docker ps # 说明只要容器还在,启不启动数据都在
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
[root@sa ~]#
[root@sa ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f24dcc07e0e5 centos "/bin/sh -c 'while t…" 15 ago Exited vigilant_hypatia
[root@sa ~]#
镜像是一种轻量级、可执行的独立软件包(类似于免安装),用来打包软件运行环境和基于运行环境开发的软件,他包含运行某个软件所需的所有内容,包括代码、运行是所需的库、环境变量及配置文件等等。
UnionFS(联合文件系统)
UnionFS:Union文件系统是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂在到同一个虚拟文件系统(unite several directories into a single virtual filesystem)下。Union文件系统时Docker镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:以此同时加载多个文件系统,单对外表现,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
Docker镜像加载原理
Docker的镜像实际上有一层一层的文件系统组成,这种层级的文件系统叫联合文件系统
Bootfs(boot file system)主要包含bootloader 和kernel,BootLoader主要是引导加载kerner,Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们经典的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs(root file system),在bootfs之上。包含的就是典型Linux系统中的/dev, /proc, /etc等标准目录文件。rootfs就是各种不同的操作系统发行版,例如:Ubuntu、CentOS等。
平时安装的虚拟机CentOS都是好几个G,但是Docker这里的才几百兆,为什么?
因为Docker镜像一般都是非常精简的,不要忘了,他用的宿主机的内核,自己只需要提供rootfs就可以了。所以镜像只需提供最基本的命令、工具和程序即可。由此,对于不同的Linux发行版,bootfs基本是一致的,rootfs会有差别,因此不同的发行版可以共用bootfs。
分层的镜像
在下载镜像时,可以注意到下载日志输出的是一层层下载的
分层的好处:
最大的好处莫过于资源共享!例如,有多个镜像都是相同的Base镜像构建而来,那么宿主机只需要在磁盘上保留一份Base镜像,同时,内存中也只需要加载一份Base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。
查看镜像分层的方式可以通过 docker inspect 命令查看
理解:
所有的Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上创建一个新的镜像层。
例如:基于Ubuntu Linux 16.04创建一个新的镜像,这就是新镜像的第一层,如果在该镜像中添加Python包,就会在基础镜像层之上创建第二个镜像层,如果继续添加,则依次往上新建。
(这里其实忽略了一个重要的概念,那就是层,怎样的添加才能构成所谓的 “层")
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部!这一层就是我们通常说的容器层,容器之下的都叫镜像层。
比较好的理解:
Docker的镜像分层
接下来,我们来看看 Docker的镜像分层机制。
Docker镜像是分层构建的,Dockerfile 中每条指令都会新建一层。例如以下 Dockerfile:
FROM ubuntu:18.04 COPY . /app RUN make /app CMD python /app/app.py
以上四条指令会创建四层,分别对应基础镜像、复制文件、编译文件以及入口文件,每层只记录本层所做的更改,而这些层都是只读层。当你启动一个容器,Docker 会在最顶部添加读写层,你在容器内做的所有更改,如写日志、修改、删除文件等,都保存到了读写层内,一般称该层为容器层,如下图所示:
事实上,容器(container)和镜像(image)的最主要区别就是容器加上了顶层的读写层。所有对容器的修改都发生在此层,镜像并不会被修改,也即前面说的 COW(copy-on-write)技术。容器需要读取某个文件时,直接从底部只读层去读即可,而如果需要修改某文件,则将该文件拷贝到顶部读写层进行修改,只读层保持不变。
每个容器都有自己的读写层,因此多个容器可以使用同一个镜像,另外容器被删除时,其对应的读写层也会被删除(如果你希望多个容器共享或者持久化数据,可以使用 Docker volume)。
最后,执行命令 docker ps -s,可以看到最后有两列 size 和 virtual size。其中 size就是容器读写层占用的磁盘空间,而 virtual size 就是读写层加上对应只读层所占用的磁盘空间。如果两个容器是从同一个镜像创建,那么只读层就是 100%共享,即使不是从同一镜像创建,其镜像仍然可能共享部分只读层(如一个镜像是基于另一个创建)。因此,docker 实际占用的磁盘空间远远小于 virtual size 的总和。
以上就是 Docker 镜像分层的主要内容,至于这些层的交互、管理就需要存储驱动程序,也即联合文件系统(UnionFS)。Docker 可使用多种驱动,如目前已经合并入 Linux 内核、官方推荐的overlay, 曾在 Ubuntu、Debian等发行版中得到广泛使用的 AUFS,以及devicemapper、zfs等等,需要根据 Docker以及宿主机系统的版本,进行合适的选择。
docker commit 提交容器成为一个新的副本(镜像)
# 命令和git类似
docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像名:[TAG]
数据如果只保存在容器中,那么容器删除,则数据丢失。
有没有什么方式可以将容器中的数据同步或者说映射至本地/宿主机的长期保存所想要的数据。
容器之间也可以共享数据。
于是,有了卷技术。简单理解就是目录的挂载,将容器内的目录挂载到宿主机上,并且容器间也是可以数据共享的。
方式一: 直接使用命令挂载 -v
# docker run -it -v 宿主机目录:容器内目录,宿主机目录可以不存在,会自动创建,最好使用绝对路径
-it # 交互式启动
-v # 卷volume 挂载
[root@sa ~]# docker run -it -v ~/mont_volume:/home centos /bin/bash
# 启动之后可根据 docker inspect 容器id 查看挂载结果
[root@sa ~]# docker inspect 0601e8ee0ec6
# 挂载以后基本上就理解成容器内核宿主机共用这个目录。
# 测试可发现,当容器停止后,在宿主机向mont_volume目录写入文件,然后重新启动容器,容器home目录可直接看到写入文件
# 即使,容器被删除,然后重新起一个容器,挂载宿主机目录还是mont_volume,那么,新容器也是可以直接看到写入文件。这也说明了,容器删除不会影响挂载的目录
# 总结,可用该方法将配置类的文件挂载出来,当我们需要修改容器配置时,无需进入容器即可修改
# 1、获取镜像
[root@sa ~]# docker pull mysql
# 2、安装mysql一定要注意配置密码
# Starting a MySQL instance is simple:
#
[root@sa ~] # docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
# ... where some-mysql is the name you want to assign to your container, my-secret-pw # is the password to be set for the MySQL root user and tag is the tag specifying the # MySQL version you want. See the list above for relevant tags.
# from https://hub.docker.com/_/mysql
# 3、启动镜像
[root@sa ~]# docker run -d -p 3310:3306 -v /home/myspl/conf:/etc/mysql/conf.d -v /home/mysql/date:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql ac4ca43eb68bc7bfadbd6da4cef7fa81f83d79eb10b9639cf8b7cbc016e50c9a
-d 后台运行
-p 端口映射
-v 卷挂载
-e 环境配置
--name 容器名字(容器名字mysql01,而mysql是镜像名)
[root@sa ~]#
# 匿名挂载
-v 容器内目录
[root@sa ~]# docker run -d -P --name nginx -v /etc/nginx nginx
# 查看所有的 volume 的情况
[root@sa ~]# docker volume ls
DRIVER VOLUME NAME
local ed11f4eedfbbdff94704282111ffe0d6a8bad21171f4c6af0be255f8a649ab27
[root@sa ~]#
# VOLIME NAME 实际上是一些具体的目录
# 现在为了对比,起一个具名的挂在容器
[root@sa ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
42ca9c1cd1447c2d6f79caa9c9197cd7083a597ce01abec60c0109018cc69b06
[root@sa ~]#
[root@sa ~]# docker volume ls
DRIVER VOLUME NAME
local ed11f4eedfbbdff94704282111ffe0d6a8bad21171f4c6af0be255f8a649ab27
local juming-nginx
# 通过 -v 卷名:容器内路径
# 查看这个卷
[root@sa ~]# docker volume inspect ed11f4eedfbbdff94704282111ffe0d6a8bad21171f4c6af0be255f8a649ab27
[
{
"CreatedAt": "2021-05-29T23:21:35+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/ed11f4eedfbbdff94704282111ffe0d6a8bad21171f4c6af0be255f8a649ab27/_data",
"Name": "ed11f4eedfbbdff94704282111ffe0d6a8bad21171f4c6af0be255f8a649ab27",
"Options": null,
"Scope": "local"
}
]
[root@sa ~]#
[root@sa ~]# docker volume inspect juming-nginx
[
{
"CreatedAt": "2021-06-26T21:55:36+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/juming-nginx/_data",
"Name": "juming-nginx",
"Options": null,
"Scope": "local"
}
]
[root@sa ~]#
[root@sa juming-nginx]# cd _data/
[root@sa _data]# ls
conf.d fastcgi_params koi-utf koi-win mime.types modules nginx.conf scgi_params uwsgi_params win-utf
总结:
所有的Docker容器的卷,在没有指定挂在目录的情况下都在 /var/lib/docker/volumes/xxxxx/_data
我们通过具名挂载可以方便的找到我们的一个卷,大多数情况下使用 具名挂载
# 如何确定是具名还是匿名挂载
-v 容器内路径 # 匿名挂载
-v 卷名:容器内路径 # 具名挂载
-v 宿主机路径:容器内路径 # 指定路径挂载
拓展:(下面所说的权限都是针对容器而言,也就是如果在容器上是只读的,但,在宿主机上是可以进行写操作的)
# 通过 -v 容器内路径 :ro :rw 来改变读写权限
# ro 只读
# rw 可读可写
# 凡是设置了这个容器权限,容器对挂载的内容的权限就被限定了
[root@sa ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
[root@sa ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
# 上面所说的权限都是针对容器而言,也就是如果在容器上是只读的,但是可以在宿主机上进行写操作(当然,宿主机本身对该路径可写)
Dockerfile是用来构建docker镜像的构建文件。命令脚本。
通过这个脚本可以生成镜像,镜像是一层一层的,脚本时一个个的命令,每个命令就是一层
# 创建一个dockerfile文件,名字可以任意,最好还是dockerfile
# 一般格式:指令(全大写) 参数
# 文件内容如下:
[root@sa Dockerfile-test]# cat dockerfile1 # 以下的每个指令就是镜像的一层
FROM centos # 将centos作为基础镜像
VOLUME ["volume01", "volume02"] # 挂载数据卷
CMD echo "----is wax ---- first --- Dockerfile----"
CMD /bin/bash
[root@sa Dockerfile-test]#
# 查看镜像生成过程
[root@sa Dockerfile-test]# docker build -f dockerfile1 -t wax-centos:0.1 .
-f 指定dockerfile文件
-t tag 目标文件
一定不要忘了生成的文件存放路径—— 末尾的那个 .(当前目录)
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos
---> 300e315adb2f
Step 2/4 : VOLUME ["volume01", "volume02"]
---> Running in 669152823e0d
Removing intermediate container 669152823e0d
---> 5e192b1bef6d
Step 3/4 : CMD echo "----is wax ---- first --- Dockerfile----"
---> Running in b65e28cc25db
Removing intermediate container b65e28cc25db
---> cf5be1a83272
Step 4/4 : CMD /bin/bash
---> Running in 8cb48405b199
Removing intermediate container 8cb48405b199
---> 09081e56f27e
Successfully built 09081e56f27e
Successfully tagged wax-centos:0.1
[root@sa Dockerfile-test]#
# 查看镜像
[root@sa Dockerfile-test]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
wax-centos 0.1 09081e56f27e 5 minutes ago 209MB
redis latest fad0ee7e917a 3 weeks ago 105MB
# 启动镜像
[root@sa Dockerfile-test]# docker run -it 09081e56f27e /bin/bash
[root@93d0f80f1c21 /]#
[root@93d0f80f1c21 /]# 可以看到已经成功进入
[root@93d0f80f1c21 /]# ls -l
total 0
...... # 篇幅问题省略
drwxr-xr-x. 2 root root 6 Nov 3 2020 srv
dr-xr-xr-x. 13 root root 0 May 15 10:53 sys
drwxrwxrwt. 7 root root 145 Dec 4 2020 tmp
drwxr-xr-x. 12 root root 144 Dec 4 2020 usr
drwxr-xr-x. 20 root root 262 Dec 4 2020 var
drwxr-xr-x. 2 root root 6 Jun 28 11:13 volume01 # 两个volume** 目录就是构建镜像时,
drwxr-xr-x. 2 root root 6 Jun 28 11:13 volume02 # 自动挂载的数据卷目录
# 可以看到两个volume实际上是匿名挂载,那么,查看其挂载的具体位置
[root@sa ~]# docker inspect 93d0f80f1c21
.....
"Mounts": [
{
"Type": "volume",
"Name": "c716f0d4f9*****bddf",
"Source": "/var/lib/docker/volumes/c716f0d4f9*****bddf/_data",
"Destination": "volume01",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "14e0ac65b55b5******d156e",
"Source": "/var/lib/docker/volumes/14e0ac65b55b5*****d156e/_data",
"Destination": "volume02",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
......
实际上,这种方式为十分常用,因为我们的目的大多需要自己构建镜像。
假如,构建镜像没有自动挂载卷,那就要自己手动挂载了, -v 卷名:容器内路径 (手动当然 具名挂载 啦)
作用:实现多个容器间的数据同步,共享。
命令: - -volumes - from container_name
一般把被挂载的容器也就是父容器 称之为数据卷容器
# 启动数据卷容器
[root@sa ~]# docker run -it --name docker-father wax-centos:0.1 /bin/bash
# 启动要和父容器共享的容器(为了简单,使用同一个镜像)
[root@sa ~]# docker run -it --name docker-child01 --volumes-from docker-father wax-centos:0.1 /bin/bash
# 在启动2个容器
[root@sa ~]# docker run -it --name docker-child02 --volumes-from docker-father wax-centos:0.1
[root@sa ~]# docker run -it --name docker-child03 --volumes-from docker-child02 centos
# 查看父容器 (123. 456是03创建的)
[root@8eb24755d5fd volume01]# ls
123.txt 456.txt child01-create child02-create
# 查看03容器
[root@sa ~]# docker attach docker-child03
[root@f2eb5b35ae88 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var volume01 volume02
[root@f2eb5b35ae88 /]#
直接给结论:
当我们删除任何一个容器时,其他容器的挂载目录不受影响。
可以看到,child03的镜像是基础的centos镜像,挂载到02上的,其自身并没有挂载volume01 、volume02,但是,使用了
–volumes-from 指令之后依然会挂载这两个目录。
另外,虽然03容器挂载的是02容器,但是停止并删除了02容器后,再在03中的volume01目录下创建文件在父容器、01中依然可以看到。另外,再把父容器删除,03中volume*目录下创建的文件,在01中还是可以看到。所以应该不是简单的拷贝。(原理我也不知奥)
另外,通过inspect可以看到,4个容器的挂载目录都是同一个地方,是不是突然明白了?也就是,即使,你把所有容器都删了,数据应该是还在的,都在宿主机上,需要注意的是,删除容器不影响数据(文件),但是在容器内删除文件是同步的,包括会同步到宿主机。其实这个比较好理解。
再另外,在宿主机上,删除宿主机上的被挂载目录,比如 /var/lib/docker/volumes/***/_data 的_data目录,那么已经启动的容器将无法在其挂载目录下进行操作——无法新建文件。就算手动将_data目录再创建出来,依然不能新建文件。需要停止后重启容器才可以新建。
Dockerfile 是用来构建docker镜像的文件,就是参数脚本!
构建步骤:
Dockerfile是面向开发的!
Dockerfile:构建文件,定义了所有步骤,源代码
Dockerimages:通过Dockerfile构建的镜像,做种发布和运行
Docker容器:容器就是镜像运行起来提供的服务
FROM # 基础镜像,一切从这里构建
MAINTAINER # 镜像的作者、维护者
RUN # 镜像构建的时候需要运行的命令
ADD # 步骤:Ubuntu镜像,可以给他加vim配置文件啥的
WORKDIR # 镜像的工作目录
VOLUME # 挂载的目录
EXPOSE # 保留端口配置
CMD # 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT # 指定这个容器启动的时候要运行的命令,可以追加命令
ONBUILD # 当构建一个被继承 Dockerfile,这个时候就会运行 ONBUILD 的指令
COPY # 类似ADD,将文件拷贝至镜像中
ENV # 构建的时候设置环境变量
以ubuntu基础镜像为例
# 先通过 docker pull ubuntu 下载一个官方的最新的ubuntu基础镜像
#这里可以先尝试一下,刚下载的镜像是不支持vim 的,等下通过dockerFile制作一个带有vim的镜像
#下面开始写dockerFile
root@wu:~/my_dockerfile# cat Dockerfile
FROM ubuntu
MAINTAINER wax<1123@qq.com>
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN apt-get update
RUN apt-get install -y vim
RUN apt-get install -y net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "------end!-------"
CMD /bin/bash
这里需要注意一点,如果是在centos系统上安装的docker,那么大概率执行以上dockerFile将会是失败的。据说是,容器使用的资源库的源是来自宿主机的,也就是centos上不能执行atp-get,而是 yum install命令
接着,我们就来构建自己的镜像(删除了安装vim过程中的打印)
# dockerFile的构建命令 可以通过 docker build --help查看
# -f 指定dockerFile文件 -t 指定目标镜像
root@wu:~/my_dockerfile# docker build -f Dockerfile -t my_ubuntu:1.0 ./
Sending build context to Docker daemon 2.048kB
Step 1/11 : FROM ubuntu
---> ff0fea8310f3
Step 2/11 : MAINTAINER wax<1123@qq.com>
---> Using cache
---> 350bceb3effd
Step 3/11 : ENV MYPATH /usr/local
---> Using cache
---> 792e5469839a
Step 4/11 : WORKDIR $MYPATH
---> Using cache
---> 1b2933c2de8f
Step 5/11 : RUN apt-get update
---> Using cache
---> cee70ef91575
Step 6/11 : RUN apt-get install -y vim
---> Using cache
---> 628163e35047
Step 7/11 : RUN apt-get install -y net-tools
---> Using cache
---> 66ceeb0d5a36
Step 8/11 : EXPOSE 80
---> Using cache
---> 1a3c9ff3333b
Step 9/11 : CMD echo $MYPATH
---> Using cache
---> b9007091be62
Step 10/11 : CMD echo "------end!--------"
---> Using cache
---> 1c85c73c79fe
Step 11/11 : CMD /bin/bash
---> Using cache
---> 66c8af1a3f90
Successfully built 66c8af1a3f90
Successfully tagged my_ubuntu:1.0
root@wu:~/my_dockerfile#
完了之后就可以运行my_ubuntu镜像,并且可以直接执行vim 以及 ifconfg等命令。这里就不做演示了。