狂神说Docker
名言金句:弱小和无知不是生存的阻碍,傲慢才是。
docker阿里云镜像地址:
yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
Docker学习
- Docker概述
- Docker安装
- Docker命令
- 镜像命令
- 容器命令
- 操作命令
- …
- Docker镜像
- 容器数据卷
- DockerFile
- Docker网络原理
- IDEA整合Docker
- Docker Compose
- Docker Swarm
- CI\CD Jenkins
知道的越多,不知道的越多!
Docker概述
Docker为什么会出现?
一款产品:开发-上线 两套环境!应用环境!应用配置!
开发—运维,问题:我在我的电脑上可以运行!版本更新,导致服务不可用!对于运维来说,考验就十分大!
环境配置是十分麻烦的,每一个及其都要部署环境(集群Redis、ES、Hadoop…)!耗时费力
发布一个项目(jar+(Redis、MySQL、jdk、ES)),项目能不能都待上环境安装打包!
之前在服务器配置一个应用的环境Redis、MySQL、jdk、ES、Hadoop,配置超麻烦,不能跨平台。
Windows开发,最后发布到Linux!
传统:开发jar,运维来做;
现在:开发打包部署上线,一套流程做完!
DevOps(开发+运维)
Docker给以上的问题,提出了解决方案!
Docker的思想就来自于集装箱;
JRE —多个应用(端口冲突) —原来都是交叉的!
隔离:Docker核心思想!打包装箱!每个箱子都是相互隔离的。
水果 生化武器
Docker通过隔离机制,可以将服务器利用到极致!
本质:所有的技术都是因为出现了一些问题,我们需要去解决,才去学习。
Docker的历史
2010年,几个搞IT的年轻人,就在美国成立了一家公司dotcloud;
做一些pass的云计算服务!LXC(Linux软件容器)有关的容器技术!
他们将自己的技术(容器化技术)命名就是Docker!
Docker刚刚诞生的时候,没有引起行业的这一!dotCloud,救活不下去!
开放源代码!
2013年,Docker开源!
越来越多的人发现Docker的有点!火了,Docker每个月会更新一个版本!
Docker能干嘛
之前的虚拟机技术
虚拟机技术的缺点:
- 资源占用十分多;
- 冗余步骤多;
- 启动很慢;
容器化技术
容器化技术不是模拟一个完整的操作系统;
内核:
内核,是一个操作系统的核心。是基于硬件的第一层软件扩充,提供操作系统的最基本的功能,是操作系统工作的基础,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。
比较Docker与虚拟机技术的不同:
- 传统虚拟机:通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统,然后在这个系统上安装和运行软件;
- Docker:容器内的应用直接运行在宿主机的内核上,容器没有自己的内核,也没有虚拟我们的硬件,所以就轻便了;
- Docker:每个容器间是互相隔离的,每个容器内都有一个属于自己的文件系统,都有一个独立的环境,互不影响;
DevOps(开发、运维)
应用更快捷的交付和部署
传统:一堆帮助文档,安装程序;
Docker:打包镜像发布测试,一键运行;
更便捷的升级和扩容
使用了Docker之后,我们部署应用就和搭积木一样!
SpringBoot 1.5、Redis 5、Tomcat 8
项目打包为一个镜像,扩展 服务器A!服务器B
更简单的系统运维
在容器化之后,我们的开发,测试环境都是高度一致的;
更高效的计算机资源利用
Docker是内核级别的虚拟化,可以再一个物理机上运行多个容器实例,服务器的性能可以被压榨到极致!
Docker的安装
Docker的基本组成
镜像(image):
docker镜像就好比是一个模板,可以通过这个模板来创建容器服务,Tomcat镜像===>run===>tomcat01容器(提供服务器),通过这个镜像可以创建多个容器(最终服务运行或者项目运行就是在容器中的)。
容器(container):
Docker利用容器技术,独立运行一个或者一组应用,通过镜像来创建的,
启动、停止、删除;基本命令;
目前就可以把这个容器理解为就是一个简易的linux系统;
仓库:
仓库就是存放镜像的地方;
仓库分为公有仓库和私有仓库;
Docker Hub(默认是国外的);
阿里云(国内镜像,可提速);
安装Docker
环境准备
- 需要会一点点的Linux的基础;
- CentOS 7
- 我们使用Xshell连接远程服务器进行操作!
环境查看
# 系统内核是3.10以上的
[root@localhost ~]# uname -r
3.10.0-862.el7.x86_64
[root@localhost ~]# 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"
安装
帮助文档:
1、卸载旧的版本
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
2、需要的安装包
yum install -y yum-utils
3、设置镜像的仓库
yum-config-manager \ --add-repo \ http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
更新yum软件包索引
yum makecache fast
4、安装docker(docker-ce:社区、ee:企业版)
yum install docker-ce docker-ce-cli containerd.io
5、启动docker
systemctl start docker
6、查看docker是否安装成功
docker version
7、运行docker容器
docker run hello-world
8、查看一下下载的这个hello-world镜像
docker images
了解:卸载docker
1. 卸载依赖
yum remove docker-ce docker-ce-cli containerd.io
2. 删除资源
rm -rf /var/lib/docker
/var/lib/docker:docker的默认工作路径
回顾HelloWorld流程
底层原理
Docker是怎么工作的?
Docker是一个Client-Server结构的系统,Docker的守护进行(即Server)运行在宿主机上,通过Socket从客户端访问!
DockerServer接收到Docker-Client的指令,就会执行这个命令!
Docker为什么比VM快?
- Docker有着比虚拟机更少的抽象层。
- Docker利用的是宿主机的内核,VM需要是Guest OS(子操作系统)。
所以说,新建一个容器的时候,Docker不需要像虚拟机一样重新加载一个操作系统内核,避免引导。虚拟机是加载Guest OS,分钟级别的,而Docker是利用宿主机的操作系统,省略了这个复杂的过程,秒级!
Docker的常用命令
官网文档:https://docs.docker.com/engine/reference/commandline/
帮助命令
docker version # 显示Docker的版本信息
docker info # 显示Docker的系统信息,包括镜像与容器数量
docker 命令 --help # 万能命令
镜像命令
docker images:查看所有本地主机上的镜像
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 9c7a54a9a43c 2 months ago 13.3kB
centos latest 5d0da3dc9764 22 months ago 231MB
# 解释
REPOSITORY 镜像的仓库源
TAG 镜像的标签
IMAGE ID 镜像的id
CREATED 镜像的创建时间
SIZE 镜像的大小
# 可选相关
-a, --all # 列出所有镜像
-q, --quiet # 只显示镜像的id
docker search搜索镜像
[root@localhost ~]# docker search tomcat # 搜索tomcat镜像
docker pull 下载镜像
[root@localhost ~]# docker pull tomcat # 下载tomcat镜像(没有指定版本,默认是最新版本)
Using default tag: latest
latest: Pulling from library/tomcat
9d19ee268e0d: Pull complete
f2b566cb887b: Pull complete
b375e6654ef5: Downloading 159.7MB/192.6MB
b375e6654ef5: Downloading 160.8MB/192.6MB
b375e6654ef5: Downloading 161.3MB/192.6MB
b375e6654ef5: Pull complete
19452d1108a6: Pull complete
b82f37793aff: Pull complete
3c7dae5d5e11: Pull complete
6e76f5caab20: Pull complete
Digest: sha256:2729ebbf9c2fa54b83298eabfd3c1f67417e3a91094ebeb080c8d9e43dc1ce78
Status: Downloaded newer image for tomcat:latest
docker.io/library/tomcat:latest
注意:
- 采用的是分层下载,docker image的核心,联合文件系统;
- Digest:签名;
- docker.io/library/tomcat:latest:真实地址;
docker rmi 删除镜像:
[root@localhost ~]# docker rmi -f hello-world # 此处的镜像名也可以换成镜像id(删除单个镜像)
[root@localhost ~]# docker rmi -f hello-world centos # 同时删除多个镜像
[root@localhost ~]# docker rmi -f $(docker images -aq) # 删除所有的镜像
容器命令
说明:我们有了镜像才可以创建容器,linux,下载一个centos镜像来测试学习
1.、下载镜像
docker pull centos
2、新建容器并启动
docker run [可选参数] image
# 参数说明
--name="Name" 容器名称tomcat01 tomcat02用来区分容器
-d 后台方式运行
-it 使用交互方式运行,进入容器查看内容
-p 指定容器的端口号 -p 8080:8080
-p ip:主机端口:容器端口
-p 主机端口:容器端口(常用)
-p 容器端口
-P 随机指定端口
3、测试,启动并进入容器
[root@localhost ~]# docker run -it centos /bin/bash
[root@486c97420bd0 /]# ls
bin etc lib lost+found mnt proc run srv tmp var
dev home lib64 media opt root sbin sys usr
[root@486c97420bd0 /]# exit # 退出命令
4、列出所有运行的容器(docker ps)
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
94b04e652301 centos "/bin/bash" 4 seconds ago Up 4 seconds sleepy_curie
dokcer ps -a # 列出当前正在运行的容器+带出历史运行过的容器
-q # 只显示容器的编号
5、退出容器
exit # 直接容器停止并退出
Ctrl+P+Q # 容器不停止退出
6、删除容器
docker rm -f 容器id # 删除指定的容器(可以不加上-f,如此则不能删除正在运行的容器)
docker rm -f $(docker ps -aq) # 删除所有的容器
7、启动和停止容器的操作
docker start 容器id # 启动容器
docker restart 容器id # 重启容器
docker stop 容器id # 停止当前正在运行的容器
docker kill 容器id # 强制停止当前容器
常用其他命令
后台启动容器
#命令docker run -d 镜像名
[root@localhost ~]# docker run -d centos
f7fac32343a688f861ca6b83410622f82fb779f33b4d95e7254b975c4563886d
#调用docker ps 发现centos停止了
#常见的坑:docker容器使用后台运行,就必须要有一个前台进程,docker发现没有应用,就会自动停止;
#同样的nginx 容器启动后,发现自己没有提供服务,就会立刻停止,就是没有程序了;
查看日志
docker logs -f -t -tail 10 容器id
#另开一个终端,输入命令:
[root@localhost ~]# docker run -it centos /bin/bash
#进入之后,输入以下命令:
[root@b5f6d7e5b519 /]# while true;do echo kuangshen;sleep 1;done
#在原终端输入,查看日志命令:
[root@localhost ~]# docker logs -t -f --tail 10 b5f6d7e5b519
2023-07-27T12:45:13.263282615Z kuangshen
2023-07-27T12:45:14.265265038Z kuangshen
...
查看容器中进程信息
#命令docker top 容器id
[root@localhost ~]# docker top 8a524532e706
UID PID PPID C STIME TTY TIME CMD
root 12611 12593 0 20:50 pts/0 00:00:00 /bin/bash
查看镜像的元数据
#命令docker inspect 容器id
[root@localhost ~]# docker inspect 8a524532e706
[
{
"Id": "8a524532e706606c79202e4e0b6d475291b1d27bb480db904007290c31f69db4",
"Created": "2023-07-27T12:50:43.282010909Z",
"Path": "/bin/bash",
"Args": [],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 12611,
"ExitCode": 0,
"Error": "",
"StartedAt": "2023-07-27T12:50:43.609984356Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
"ResolvConfPath": "/var/lib/docker/containers/8a524532e706606c79202e4e0b6d475291b1d27bb480db904007290c31f69db4/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/8a524532e706606c79202e4e0b6d475291b1d27bb480db904007290c31f69db4/hostname",
"HostsPath": "/var/lib/docker/containers/8a524532e706606c79202e4e0b6d475291b1d27bb480db904007290c31f69db4/hosts",
"LogPath": "/var/lib/docker/containers/8a524532e706606c79202e4e0b6d475291b1d27bb480db904007290c31f69db4/8a524532e706606c79202e4e0b6d475291b1d27bb480db904007290c31f69db4-json.log",
"Name": "/sweet_brattain",
"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,
"ConsoleSize": [
24,
80
],
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "host",
"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",
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": [],
"BlkioDeviceWriteBps": [],
"BlkioDeviceReadIOps": [],
"BlkioDeviceWriteIOps": [],
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"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/8bfb921e310b7232ffcccb78e209461fd9cc8ec21cbae4112692d76c9e582609-init/diff:/var/lib/docker/overlay2/1af059a2e44d0a09b9672b2c72768a920d0d015359a90888a3d9e4e83913c18b/diff",
"MergedDir": "/var/lib/docker/overlay2/8bfb921e310b7232ffcccb78e209461fd9cc8ec21cbae4112692d76c9e582609/merged",
"UpperDir": "/var/lib/docker/overlay2/8bfb921e310b7232ffcccb78e209461fd9cc8ec21cbae4112692d76c9e582609/diff",
"WorkDir": "/var/lib/docker/overlay2/8bfb921e310b7232ffcccb78e209461fd9cc8ec21cbae4112692d76c9e582609/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "8a524532e706",
"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": "20210915",
"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": "34c854b249a154eb72474fffe4a43ae2d0db25afd6d1e3521acf4be33da98328",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/docker/netns/34c854b249a1",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "1dd6552d21d4f53abda6abb167e661b82ab51668a337f11f938081bc9a2d10d2",
"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": "a62c1bab69896f54a38f459edf7ce6c84b02e46c68f57ffd88d66c6ed2da94b6",
"EndpointID": "1dd6552d21d4f53abda6abb167e661b82ab51668a337f11f938081bc9a2d10d2",
"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
}
}
}
}
]
进入当前正在运行的容器
方式一:
我们通常容器都是使用后台方式运行的,需要进入容器,修改一些配置
命令:docker exec -it 容器id /bin/bash
#测试
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5ae382de8660 centos "/bin/bash" 21 seconds ago Up 20 seconds wizardly_nightingale
[root@localhost ~]# docker exec -it 5ae382de8660 /bin/bash
[root@5ae382de8660 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
#查看所有进程信息
[root@5ae382de8660 /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 12:59 pts/0 00:00:00 /bin/bash
root 15 0 0 13:00 pts/1 00:00:00 /bin/bash
root 30 15 0 13:02 pts/1 00:00:00 ps -ef
#退出容器
[root@5ae382de8660 /]# exit
exit
[root@localhost ~]#
方式二:
命令:docker attach 容器id
[root@localhost ~]# docker attach 5ae382de8660
[root@5ae382de8660 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var
从容器内拷贝文件到主机上
命令:docker cp 容器id:容器内路径 目的主机路径
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b4122b05506 centos "/bin/bash" 12 seconds ago Up 11 seconds beautiful_dewdney
[root@localhost ~]# docker exec -it 5b4122b05506 /bin/bash
[root@5b4122b05506 /]# ls
bin etc lib lost+found mnt proc run srv tmp var
dev home lib64 media opt root sbin sys usr
[root@5b4122b05506 /]# cd tmp
[root@5b4122b05506 tmp]# ls
ks-script-4luisyla ks-script-o23i7rc2 ks-script-x6ei4wuu
[root@5b4122b05506 tmp]# touch lq.txt
[root@5b4122b05506 tmp]# ls
ks-script-4luisyla ks-script-o23i7rc2 ks-script-x6ei4wuu lq.txt
[root@5b4122b05506 tmp]# exit
exit
[root@localhost ~]# ps
PID TTY TIME CMD
11873 pts/1 00:00:00 bash
14635 pts/1 00:00:00 ps
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5b4122b05506 centos "/bin/bash" About a minute ago Up About a minute beautiful_dewdney
[root@localhost ~]# docker cp 5b4122b05506:/tmp/lq.txt /tmp
Successfully copied 1.54kB to /tmp
拷贝是一个手动过程,未来我们使用容器卷技术,可以实现自动;
作业练习
docker安装nginx
1、搜索镜像 docker search 镜像名(此处省略,默认下载最新的)
2.、下载镜像 docker pull 镜像名
3、运行测试
[root@localhost ~]# docker pull nginx
[root@localhost ~]# docker run -d --name nginx01 -p 3304:80 nginx
[root@localhost ~]# docker top ed28250c389c
UID PID PPID C STIME TTY TIME CMD
root 15140 15121 0 21:23 ? 00:00:00 nginx: master process nginx -g daemon off;
101 15193 15140 0 21:23 ? 00:00:00 nginx: worker process
[root@localhost ~]# curl localhost:3304
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
端口暴露的概念
思考问题:我们每次改动nginx配置文件,都需要进入容器内部?十分麻烦,我要是可以在容器外部提供一个映射路径,达到在外部修改文件,容器内部就可以自动修改?-v 容器卷
Docker镜像讲解
镜像是什么
镜像是一种轻量级,可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时库、环境变量和配置文件。
所有的应用,直接打包docker镜像,就可以直接跑起来!
镜像的获取方式:
- 从远程仓库下载
- 拷贝
- 制作一个镜像DockerFile
Docker镜像加载原理
UnionFS(联合文件系统)
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层,轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下。Union文件系统是Docker镜像的基础,镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录;
Docker镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统就是UnionFS。
系统启动引导加载过程
bootfs(boot file system) 主要包含bootloader(加载器)和kernel(内核), bootloader(加载器)主要是引导加载kernel(内核), Linux刚启动时会加载bootfs文件系统,Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含bootloader(加载器)和kernel(内核)。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs(平时开机即启动系统时需要用到bootloader(加载器),开机的速度非常的久,但Docker的容器的内核用的是主机的内核,主机已经启动内核,容器不需要再用到bootloader(加载器)来启动内核,所以Docker容器的启动速度非常的快)
rootfs (root file system),在bootfs之上。包含的就是典型Linux系统中的/dev, /proc, /bin, /etc等标准目录和文件,简单来说rootfs就是各种不同的操作系统发行版,比如Ubuntu , Centos等等。(rootfs可以很小,只需包含最基本的命令、工具和程序即可,而内核直接使用主机的kernel(内核)即可,自己只需要提供rootfs即可,所以传统安装操作系统镜像需要几个G的大小,而Docker用来安装容器的操作系统镜像只需要几百MB即可;而且对于不同的Linux发行版,它们的内核都是Linux的内核,所以bootfs基本保持一致,rootfs会有差别,因此不同的Linxu发行版可以公用bootfs)
bootfs是系统引导过程中所使用的文件系统,rootfs是操作系统运行时所使用的文件系统;
分层理解
分层的镜像
我们可以去下载一个进行,注意观察下载的日志输出,可以看到是一层一层下载!
思考:为什么Docker镜像要采用这种分层的结构呢?
最大的好处,我觉得莫过于是资源共享了!比如有多个镜像都从相同的Base镜像构建而来的,那么宿主机只需要在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。
查看镜像分层的方式可以通过:docker image inspect命令
[root@localhost ~]# docker image inspect redis:latest
理解:
所有的Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,例如基于Ubuntu Linux 16.04创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加python包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层。
在添加额外镜像层的同事,镜像始终保持着当前所有镜像层的组合。
特点
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部!
这一层就是我们通常说的容器层,容器之下都叫镜像层。
如何提交一个自己的镜像
commit镜像
docker commit 提交容器成为一个新的版本
命令和git原理类似
docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像名:[tag]
实战测试
#启动一个tamcat
[root@localhost ~]# docker run -it -p 8080:8080 tomcat
#发现这个默认的tomcat是没有webapp应用,镜像的原因,官方的镜像默认webapps下面是没有文件的!
#我自己拷贝基本的文件(从webapps.dist)
root@0f329b194534:/usr/local/tomcat# cp -r webapps.dist/* webapps/
#提交自己的镜像
[root@localhost ~]# docker commit -a="lq" -m="add webapps ap" 0f329b194534 tomcat02:1.0
学习方式说明:理解概念,但是一定要时间,最后时间和理论相结合,一次搞定这个知识!
如果你想要保存当前容器的状态,就可以通过commit来提交,获得一个镜像,
就好比我们以前学习VM时候,快照!
到了这里才算是入门Docker!认真吸收练习!
容器数据卷
什么是容器数据卷
docker的理念回顾
将应用和环境打包成一个镜像!
数据?如果数据都在容器中,那么我们容器删除,数据就会丢失!需求:数据可以持久化
MySQL,容器删了,删库跑路!需求:MySQL数据可以存储在本地!
容器之间可以有一个数据共享的技术!Docker容器中产生的数据,同步到本地!
这就是卷技术!目录的挂在,将我们容器内的目录,挂载到Linux上面(宿主机)!
总结一句话:容器的持久化和同步操作!容器间也可以数据共享的
使用数据卷
方式一:直接使用命令来挂载
docker run -it -v 主机目录:容器内目录
修改主机目录下的文件,容器内的目录文件也会发生相应的变化(包括删除)。
实战:安装MySQL
#获取镜像
docker pull mysql
#运行容器,需要做数据挂载!#安装启动mysql,需要配置密码的,这是要注意点!
#官方测试:docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:tag
#启动我们的mysql
-d 后台运行
-p 端口映射
-v 卷挂载
-e 环境配置
--name 容器名字
docker run -d -p 3306:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /hone/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql
注意:即使我们将容器删除之后,挂载到本地的数据卷依旧没有丢失,这就实现了容器数据持久化功能!
具名和匿名挂载
# 匿名挂载
-v 容器内路径!
-P 随机端口
docker run -d -P --name nginx01 -v /etc/nginx nginx
# 查看所有的volume的情况
docker volume ls
DRIVER VOLUME NAME
local 2b89da43cd43b0e513ab227ad16a4e56a66da53493b4f8a58c0e8b9e4de2d299
# 这里发现,上面这种就是匿名挂载,我们在 -v 只写了容器内的路径,没有些容器外的路径!
# 具名挂载
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
# 通过-v 卷名:容器内路径
#查看一下这个卷
docker volume inspect juming-nginx
所有的docker容器内的卷,没有指定目录的情况下都在/var/lib/docker/volumes/xxx/_data
我们通过具名挂载可以方便的找到我们的一个卷,大多数情况再使用的是:具名挂载!
# 如何确定是具名挂载还是匿名挂载,还是执行路径挂载!
-v 容器内路径 # 匿名挂载
-v 卷名:容器内路径 # 具名挂载
-v 宿主机路径:容器内路径 # 指定路径挂载!
拓展:
# 通过 -v 容器内路径:ro rw 改变读写权限
ro readonly # 只读
rw readwrite # 可读可写
# 一旦设置了容器权限,容器对我们挂载出来的内容就有了限定
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
# ro 只要看到ro就说明这个路径只能通过宿主机来操作,容器内无法操作!
DockerFile
初试Dockerfile
Dockerfile就是用来构建docker镜像的构建文件!命令脚本!先体验一下!
通过这个脚本可以生成镜像,镜像是一层一层的,脚本一个个的命令,每个命令都是一层!
# 创建一个dockerfile文件,名字可以随机,建议Dockerfile
#文件内容
FROM centos
# 挂载容器内的目录,比如:volume01不存在,则会在根陆目录下创建
VOLUME ["volume01","volume02"]
CMD echo "---end---"
CMD /bin/bash
注意:这里的每一个命令,都是镜像的一层
查看一下挂在卷的路径
测试一下刚才的文件是否同步出去了!
这种方式我们未来使用的十分多,因为我们通常会构建自己的镜像!
假设构建镜像时没有挂载卷,要手动镜像挂载 -v 卷名:容器内路径
数据卷容器
多个mysql同步数据
#通过--volumes-from 进行继承,实现容器之间的数据共享
docker run -it --name docker02 --volumes-from docker01 139235ade348 /bin/bash
容器之间的数据是互相拷贝的
多个mysql同步数据!
docker run -d -p 3310:3306 -v /etc/mysql/conf.d -v /var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7
docker run -d -p 3310:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7
这个时候,就可以实现两个容器的数据同步
结论:
容器之间配置信息的传递,数据卷容器的声明周期一直持续到没有容器使用为止。
但是一旦持久化到了本地,这个时候,本地的数据是不会删除的!
DockerFile
dockerfile是用来构建docker镜像的文件!命令参数脚本!
构建步骤:
1、编写一个dockerfile文件;
2、docker build 构建成为一个镜像;
3、docker run 运行镜像;
4、docker push 发布镜像(DockerHub、阿里云镜像仓库)
DockerFile构建过程
基础知识:
1、每个保留关键字(指令)都是必须是大写字母;
2、执行从上到下顺序执行;
3、# 表示注释;
4、每一个指令都会创建提交一个新的镜像层,并提交!
dockerfile是面向开发的,我们以后要发布项目,做镜像,就需要编写DockerFile文件,这个文件十分简单!
Docker镜像逐渐成为企业交付的标准,必须要掌握!
步骤:开发、部署、运维缺一不可!
DockerFile:构建文件,定义了一切步骤,源代码
DockerImages:通过DockerFile构建生成的镜像,最终发布和运行的产品!
Docker容器:容器就是镜像运行起来的服务器
DockerFile的指令
指令 | 描述 |
---|---|
FROM | 它的妈妈是谁(基础镜像) |
MAINTAINER | 告诉别人,你创造了它(维护者信息) |
RUN | 你先让它干啥(把命令前面加上RUN) |
ADD | 往它肚子里放点文件(COPY文件,会自动解压) |
WORKDIR | 我是cd,今天刚化了妆(当前工作目录) |
VOLUME | 给我一个存放行李的地方(目录挂载) |
EXPOSE | 我要打开的门是啥(端口) |
CMD | 指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代 |
ENTRYPOINT | 指定这个容器启动时候要运行的命令 |
COPY | 类似ADD,将我们文件拷贝到镜像中 |
ENV | 构建的时候设置环境变量 |
实战测试
Docker Hub中99%镜像都是从这个基础镜像过来的FROM scratch,然后配置需要的软件和配置来进行构建
centos镜像构建的dockerfile
FROM scratch
ADD centos-7-x86_64-docker.tar.xz /
LABEL \
org.label-schema.schema-version="1.0" \
org.label-schema.name="CentOS Base Image" \
org.label-schema.vendor="CentOS" \
org.label-schema.license="GPLv2" \
org.label-schema.build-date="20201113" \
org.opencontainers.image.title="CentOS Base Image" \
org.opencontainers.image.vendor="CentOS" \
org.opencontainers.image.licenses="GPL-2.0-only" \
org.opencontainers.image.created="2020-11-13 00:00:00+00:00"
CMD ["/bin/bash"]
创建一个自己的centos(dockerfile文件)
FROM centos
MAINTAINER author
ENV MYPATH /usr/local
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools
EXPOSE 80
CMD echo $MYPATH
CMD echo "-----end-----"
CMD /bin/bash
构建命令
docker build -f dockerfile -t mycentos:1.0.0 .
Tomcat实战
1、准备镜像文件,tomcat压缩包,jdk的压缩包;
2、编写dockerfile文件,官方命名Dockerfile,build会自动寻找这个文件,就不需要-f指定了!
FROM centos:latest
MAINTAINER 123456@qq.com
COPY readme.txt /usr/local/redme.txt
ADD jdk-8u381-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-9.0.78.tar.gz /usr/local/
ENV MYPATH /usr/local
WORKDIR $MYPATH
ENV JAVA_HOME /usr/local/jdk1.8.0_381
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.78
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.78
ENV PATH $PATH;$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin
EXPOSE 8080
# 此处的CMD命令不知为什么没有生效
CMD ["sh","/usr/local/apache-tomcat-9.0.78/bin/startup.sh"]
3、构建镜像
[root@localhost tomcat]# docker build -t mytomcat:1.0.0 .
4、启动镜像;
5、访问测试
6、发布项目(由于做了卷挂载,我们直接在本地编写项目就可以发布了!)
地址:https://hub.docker.com/
[root@localhost tomcat]# docker login --help
Usage: docker login [OPTIONS] [SERVER]
Log in to a registry.
If no server is specified, the default is defined by the daemon.
Options:
-p, --password string Password
--password-stdin Take the password from stdin
-u, --username string Username
7、登录完毕后就可以提交镜像了,就是一步:docker push mytomcat:1.0.0
提交的时候也是按照镜像的层级来进行提交的!
阿里云镜像
1、登录阿里云
2、找到容器镜像服务
3、创建命名空间
4、创建容器镜像
5、浏览阿里云
Docker网络
清空所有环境
docker rm -f $(docker ps -a)
docker rmi -f $(docker images -aq)
测试
三个网络
问题:docker是如何处理容器网络访问的?
# 启动一个tomcat
[root@localhost ~]# docker run -d -it -p 8080:8080 --name tomcat01 tomcat
# 查看容器的内部网络地址(进入容器内部)
ip addr
root@b236a2aba624:/usr/local/tomcat# ip addr 发上线容器启动的时候会得到一个ip地址,docker分配的!
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
6: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
# 如何ip命令找不到,则执行如下名之后,在执行ip addr
apt-get update & apt-get install -y iproute2
# 思考:linux能不能ping通容器内部!
[root@localhost ~]# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.041 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.040 ms
64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.042 ms
64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.040 ms
# linux可以ping通docker容器内部
原理
1、我们每启动一个docker容器,docker就会给docker容器分配一个ip,我们只要安装了docker,就会有一个网卡docker0桥接模式,使用的技术是veth-pair技术!
- 一个容器带来的网卡,都是一对对的;
- veth-pair就是一堆的虚拟设备解耦,他们都是成对出现的,一段连接着协议,一段彼此相连;
- 正因为有这个特性,veth-pair充当一个桥梁,连接各种虚拟网络设备的;
- OpenStac,Docker容器之间的连接,OVS的连接,都是使用veth-pair技术;
结论:tomcat01和tomcat02是公用的一个路由器,docker0
所有的容器不指定网络的情况下,都是docker0路由的,docker会给我们的容器分配一个默认的可用IP
小结
Docker使用的是Linux的桥接,宿主机中是一个Docker容器的网桥docker0;
Docker中的所有的网络接口都是虚拟的。虚拟的转发效率高!(内网传递文件!)
只要容器删除,对应网桥一对就没了!
–link
思考一个场景,我们编写了一个微服务,database url=ip:,项目不重启,数据库ip换掉了,我们希望可以处理这个问题,可以名字来进行访问容器?
[root@localhost ~]# docker exec -it tomcat01 ping tomcat02
ping: tomcat02: Name or service not known
# 如何解决呢?
# 通过--link既可以解决网络联通问题
[root@localhost ~]# docker run -d -P --name tomcat03 --link tomcat01 tomcat
f30f91b517dd43f8689e7f20e7b02dc381f78cba492bc3687978b5346504e5f4
[root@localhost ~]# docker exec -it tomcat03 ping tomcat01
PING tomcat01 (172.17.0.2) 56(84) bytes of data.
64 bytes from tomcat01 (172.17.0.2): icmp_seq=1 ttl=64 time=0.187 ms
64 bytes from tomcat01 (172.17.0.2): icmp_seq=2 ttl=64 time=0.051 ms
64 bytes from tomcat01 (172.17.0.2): icmp_seq=3 ttl=64 time=0.052 ms
其实这个tomcat03就是在本地配置了tomcat02的配置?
# 查看hosts配置,在这里原理发现!
[root@localhost ~]# docker exec -it tomcat03 cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 tomcat01 b236a2aba624
172.17.0.3 f30f91b517dd
本质探究:–link就是我们在hosts配置中添加了一个172.17.0.2 tomcat01 b236a2aba624
我们现在在玩Docker已经不建议使用–link了!
自定义网络!
自定义网络
查看所有的docker网络
[root@localhost ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
f1e20468cd30 bridge bridge local
b9404c3c4ce9 host host local
0c1cc9011159 none null local
网络模式
- bridge:桥接docker(默认,自己自定义网络也使用bridge模式)
- none:不配置网络
- host:和宿主机共享网络
- container:容器网络联通!(用的少,局限很大)
测试
# 我们直接启动的命令 --net bridge,而这个就是我们的docker0
docker run -d -P --name tomcat01 tomcat
docker run -d -P --name tomcat01 --net bridget tomcat
# docker0特点:默认,域名不能访问,--link可以打通连接!
# 我们可以自定义一个网络
# --driver bridge 网络模式
# --subnet 192.168.0.0/16 子网掩码(16是起始位置,16:256*256,24则是:256)
# --gateway 192.168.0.1 网关
[root@localhost ~]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
6f1fbc563df6baa7eba88c28266cc72d1dcdac7e85984368aab092f158650c6b
[root@localhost ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
f1e20468cd30 bridge bridge local
b9404c3c4ce9 host host local
6f1fbc563df6 mynet bridge local
0c1cc9011159 none null local
[root@localhost ~]# docker run -d -P --name tomcat-net-01 --net mynet tomcat
aee0dd88c681d2aa64533f949bb2f20ad47a39264de3ab80fbdea547d98298fc
[root@localhost ~]# docker run -d -P --name tomcat-net-02 --net mynet tomcat
ef9dcb27150c3e0b04d2e20d29d5dc9f9b45ca30176d689a819ebb5c97fa4886
[root@localhost ~]# docker network inspect mynet
[
{
"Name": "mynet",
"Id": "6f1fbc563df6baa7eba88c28266cc72d1dcdac7e85984368aab092f158650c6b",
"Created": "2023-08-13T11:13:25.869847895+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": {},
"Config": [
{
"Subnet": "192.168.0.0/16",
"Gateway": "192.168.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"aee0dd88c681d2aa64533f949bb2f20ad47a39264de3ab80fbdea547d98298fc": {
"Name": "tomcat-net-01",
"EndpointID": "4da28302016a97c9e5437202286b8a5d35c925daf1592122c405816564aad281",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
},
"ef9dcb27150c3e0b04d2e20d29d5dc9f9b45ca30176d689a819ebb5c97fa4886": {
"Name": "tomcat-net-02",
"EndpointID": "82fcb869bd200ca62a06199823104fb083296e3d0200b8d3139982aabba461f4",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
自定义的网络即使不使用–link,也可以ping通名字;
我们自定义的网络docker都已经帮我们维护好了对应的关系,推荐我们平时这样使用网络!
好处:
redis:不同的集群使用不同的网络,保证集群是安全和健康的;
mysql:不同的集群使用不同的网络,保证集群是安全和健康的
网络连通
# 测试打通 tomcat01 与mynet
# 联通之后就是将tomcat01放到mynet网络下
# 一个容器两个ip地址
# 阿里云服务:公网ip 私网ip
[root@localhost ~]# docker exec -it tomcat01 ping tomcat-net-01
PING tomcat-net-01 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat-net-01.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.063 ms
64 bytes from tomcat-net-01.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.088 ms
64 bytes from tomcat-net-01.mynet (192.168.0.3): icmp_seq=3 ttl=64 time=0.051 ms
结论:加入要跨网络操作别人,就需要使用docker network connect连通!
SpringBoot微服务打包Docker镜像
1、创建一个springboot项目;
2、pom同级目录下创建Dockerfile文件;
FROM openjdk:8
COPY *.jar /app.jar
CMD ["--server.port=8080"]
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
3、将springboot项目打出来的jar与Dockerfile文件,传输至Linux机器上;
4、构建docker镜像;
docker build -f Dockerfile -t xxx:1.0.0 .
5、发布自定义的docker镜像
[root@localhost idea]# docker push springboot-docker:1.0.0