Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。
Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。
容器是完全使用沙箱机制,相互之间不会有任何接口(类似 iPhone 的 app),更重要的是容器性能开销极低。
Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版),我们用社区版就可以了。
Web 应用的自动化打包和发布。
自动化测试和持续集成、发布。
在服务型环境中部署和调整数据库或其他的后台应用。
从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。
1、快速,一致地交付您的应用程序
Docker 允许开发人员使用您提供的应用程序或服务的本地容器在标准化环境中工作,从而简化了开发的生命周期。
容器非常适合持续集成和持续交付(CI / CD)工作流程,请考虑以下示例方案:
您的开发人员在本地编写代码,并使用 Docker 容器与同事共享他们的工作。
他们使用 Docker 将其应用程序推送到测试环境中,并执行自动或手动测试。
当开发人员发现错误时,他们可以在开发环境中对其进行修复,然后将其重新部署到测试环境中,以进行测试和验证。
测试完成后,将修补程序推送给生产环境,就像将更新的镜像推送到生产环境一样简单。
2、响应式部署和扩展
Docker 是基于容器的平台,允许高度可移植的工作负载。Docker 容器可以在开发人员的本机上,数据中心的物理或虚拟机上,云服务上或混合环境中运行。
Docker 的可移植性和轻量级的特性,还可以使您轻松地完成动态管理的工作负担,并根据业务需求指示,实时扩展或拆除应用程序和服务。
3、在同一硬件上运行更多工作负载
Docker 轻巧快速。它为基于虚拟机管理程序的虚拟机提供了可行、经济、高效的替代方案,因此您可以利用更多的计算能力来实现业务目标。Docker 非常适合于高密度环境以及中小型部署,而您可以用更少的资源做更多的事情。
Docker 包括三个基本概念:
Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。
Docker 容器通过 Docker 镜像来创建。
概念 | 说明 |
---|---|
Docker 镜像(Images) | Docker 镜像是用于创建 Docker 容器的模板,比如 Ubuntu 系统。 |
Docker 容器(Container) | 容器是独立运行的一个或一组应用,是镜像运行时的实体。 |
Docker 客户端(Client) | Docker 客户端通过命令行或者其他工具使用 Docker SDK (https://docs.docker.com/develop/sdk/) 与 Docker 的守护进程通信。 |
Docker 主机(Host) | 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。 |
Docker Registry | Docker 仓库用来保存镜像,可以理解为代码控制中的代码仓库。Docker Hub(https://hub.docker.com) 提供了庞大的镜像集合供使用。一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。 |
Docker Machine | Docker Machine是一个简化Docker安装的命令行工具,通过一个简单的命令行即可在相应的平台上安装Docker,比如VirtualBox、 Digital Ocean、Microsoft Azure。 |
docker官方资料http://www.dockerinfo.net/document
更换rockylinux8的国内镜像源
sed -e 's|^mirrorlist=|#mirrorlist=|g' \-e 's|^#baseurl=http://dl.rockylinux.org/$contentdir|baseurl=https://mirrors.aliyun.com/rockylinux|g' \-i.bak \/etc/yum.repos.d/Rocky-*.repo
yum makecache
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum -y install docker-ce
systemctl start docker
systemctl enable docker
docker version
了解:卸载docker
yum remove docker-ce docker-ce-cli containerd.io
rm -rf /var/docker #docker的默认工作路径
配置镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://5yq2y3im.mirror.aliyuncs.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker
docker为什么比VM快?
1、docker有着比虚拟机更少的抽象层
2、docker利用的是宿主机的内核,VM需要是Guest OS,
所以说新建一个容器的时候,docker不需要像虚拟机一样重新加载一个操作系统内核,避免引导,虚拟机是加载Guest OS,分钟级别的,而docker是利用宿主机的操作系统, 跳过了这个复杂过程,秒级。
docker官方文档:https://docs.docker.com/engine/reference/commandline/docker/
docker version #版本信息
docker info #docker的系统信息,包括镜像和容器
docker --help
docker images 查看所有本地主机上的镜像
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 9c7a54a9a43c 4 weeks ago 13.3kB
mysql latest 3218b38490ce 17 months ago 516MB
REPOSITORY #镜像仓库源
TAG #镜像标签
IMAGE ID #镜像ID
CREATED #创建时间
SIZE #大小
#可选项
-a, --all #列出所有镜像
-q, --quiet #只显示镜像的ID
docker pull 下载镜像
[root@docker ~]# docker pull mysql:5.7
5.7: Pulling from library/mysql
72a69066d2fe: Already exists
93619dbc5b36: Already exists
99da31dd6142: Already exists
626033c43d70: Already exists
37d5d7efb64e: Already exists
ac563158d721: Already exists
d2ba16033dad: Already exists
0ceb82207cd7: Pull complete
37f2405cae96: Pull complete
e2482e017e53: Pull complete
70deed891d42: Pull complete
Digest: sha256:f2ad209efe9c67104167fc609cca6973c8422939491c9345270175a300419f94
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
docker rmi 删除镜像
docker rmi 镜像ID #删除指定的镜像
docker rmi $(docker images -qa) #删除全部的镜像
docker run [可选参数] image
#参数说明
-d, --detach=false, 指定容器运行于前台还是后台,默认为false
-i, --interactive=false, 打开STDIN,用于控制台交互
-t, --tty=false, 分配tty设备,该可以支持终端登录,默认为false
-u, --user="", 指定容器的用户
-a, --attach=[], 登录容器(必须是以docker run -d启动的容器)
-w, --workdir="", 指定容器的工作目录
-c, --cpu-shares=0, 设置容器CPU权重,在CPU共享场景使用
-e, --env=[], 指定环境变量,容器中可以使用该环境变量
-m, --memory="", 指定容器的内存上限
-P, --publish-all=false, 指定容器暴露的端口
-p, --publish=[], 指定容器暴露的端口
-h, --hostname="", 指定容器的主机名
-v, --volume=[], 给容器挂载存储卷,挂载到容器的某个目录
--volumes-from=[], 给容器挂载其他容器上的卷,挂载到容器的某个目录
--cap-add=[], 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cap-drop=[], 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
--cidfile="", 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
--cpuset="", 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
--device=[], 添加主机设备给容器,相当于设备直通
--dns=[], 指定容器的dns服务器
--dns-search=[], 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
--entrypoint="", 覆盖image的入口点
--env-file=[], 指定环境变量文件,文件格式为每行一个环境变量
--expose=[], 指定容器暴露的端口,即修改镜像的暴露端口
--link=[], 指定容器间的关联,使用其他容器的IP、env等信息
--lxc-conf=[], 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
--name="", 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
--net="bridge", 容器网络设置:
bridge 使用docker daemon指定的网桥
host //容器使用主机的网络
container:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源
none 容器使用自己的网络(类似--net=bridge),但是不进行配置
--privileged=false, 指定容器是否为特权容器,特权容器拥有所有的capabilities
--restart="no", 指定容器停止后的重启策略:
no:容器退出时不重启
on-failure:容器故障退出(返回值非零)时重启
always:容器退出时总是重启
docker ps 查看容器
docker rm -f $(docker ps -aq) #删除所有容器
docker start 容器id #启动
docker restart 容器ID #重启
docker stop 容器ID #停止
docker kill 容器ID #强制删除
查看日志
docker logs [OPTIONS] CONTAINER
docker logs --help
-- tf #显示日志
--tail number #显示日志条数
[root@docker ~]# docker logs -tf --tail 10 57766036a15a
2023-06-05T01:39:34.157079141Z qhz
2023-06-05T01:39:35.159944994Z qhz
2023-06-05T01:39:36.163022053Z qhz
2023-06-05T01:39:37.165957938Z qhz
2023-06-05T01:39:38.169695142Z qhz
2023-06-05T01:39:39.174819925Z qhz
2023-06-05T01:39:40.179299989Z qhz
2023-06-05T01:39:41.184506664Z qhz
docker top 容器ID
^C
[root@docker ~]# docker top 57766036a15a
UID PID PPID C STIME TTY TIME CMD
root 2119 2099 0 09:36 ? 00:00:00 /bin/sh -c while true;do echo qhz;sleep 1;done
root 2548 2119 0 09:42 ? 00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1
docker inspect 容器ID
[root@docker ~]# docker inspect 57766036a15a
[
{
"Id": "57766036a15a56b63dcbc0c4187062dad39b15d93c2e9726fef9d9c7a7b2f4b3",
"Created": "2023-06-05T01:36:09.626896374Z",
"Path": "/bin/sh",
"Args": [
"-c",
"while true;do echo qhz;sleep 1;done"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 2119,
"ExitCode": 0,
"Error": "",
"StartedAt": "2023-06-05T01:36:10.126955207Z",
"FinishedAt": "0001-01-01T00:00:00Z"
},
"Image": "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
"ResolvConfPath": "/var/lib/docker/containers/57766036a15a56b63dcbc0c4187062dad39b15d93c2e9726fef9d9c7a7b2f4b3/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/57766036a15a56b63dcbc0c4187062dad39b15d93c2e9726fef9d9c7a7b2f4b3/hostname",
"HostsPath": "/var/lib/docker/containers/57766036a15a56b63dcbc0c4187062dad39b15d93c2e9726fef9d9c7a7b2f4b3/hosts",
"LogPath": "/var/lib/docker/containers/57766036a15a56b63dcbc0c4187062dad39b15d93c2e9726fef9d9c7a7b2f4b3/57766036a15a56b63dcbc0c4187062dad39b15d93c2e9726fef9d9c7a7b2f4b3-json.log",
"Name": "/centos",
"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,
84
],
"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/cf98ae2892b5278668f845001a0b8670d38eeb5026b9fbf7aec1aa40435dc372-init/diff:/var/lib/docker/overlay2/6eb4b884bcd4b4e94a2aa731c20fc18c87d6c03aced64b7707b2a96235ad0e9f/diff",
"MergedDir": "/var/lib/docker/overlay2/cf98ae2892b5278668f845001a0b8670d38eeb5026b9fbf7aec1aa40435dc372/merged",
"UpperDir": "/var/lib/docker/overlay2/cf98ae2892b5278668f845001a0b8670d38eeb5026b9fbf7aec1aa40435dc372/diff",
"WorkDir": "/var/lib/docker/overlay2/cf98ae2892b5278668f845001a0b8670d38eeb5026b9fbf7aec1aa40435dc372/work"
},
"Name": "overlay2"
},
"Mounts": [],
"Config": {
"Hostname": "57766036a15a",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"Cmd": [
"/bin/sh",
"-c",
"while true;do echo qhz;sleep 1;done"
],
"Image": "centos:latest",
"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": "62f0d39d744ec7ce163f311427c5a0f1d3856287eeb7d4310e075b3bfa6a5b78",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": {},
"SandboxKey": "/var/run/docker/netns/62f0d39d744e",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "300cd62f0d5dd7b89359367cda3b0fd72e16fcc6ff292ad45ca23b0ccbae13e9",
"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": "4670e51daae0e26f088a8d98d3ece5d24fbf60eb1298619ff2aa210373a6e747",
"EndpointID": "300cd62f0d5dd7b89359367cda3b0fd72e16fcc6ff292ad45ca23b0ccbae13e9",
"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 #进入容器后开启一个新的终端
docker attach 容器ID #进入容器正在执行的终端
docker cp 容器ID:容器内路径 目的主机的路径
#拷贝是一个手动过程,未来我们使用-v逻辑卷,实现自动同步
[root@docker ~]# docker pull nginx
[root@docker ~]# docker images
[root@docker ~]# docker run -d --name nginx1 -p 3344:80 nginx
c92cc2fc9dfa13aba0ef293934298454dff1e9ecdaa42af18e90eede17d050fb
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c92cc2fc9dfa nginx "/docker-entrypoint.…" 10 seconds ago Up 7 seconds 0.0.0.0:3344->80/tcp, :::3344->80/tcp nginx1
[root@docker ~]# curl localhost:3344
<!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>
[root@docker ~]# docker pull tomcat
[root@docker ~]# docker run -d --name tomcat1 -p 3355:8080 tomcat:latest
804560e03f7263d6d1103d67d900cd6c08bf0b9ba404c89d50b4218e1e453848
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
804560e03f72 tomcat:latest "catalina.sh run" 25 seconds ago Up 23 seconds 0.0.0.0:3355->8080/tcp, :::3355->8080/tcp tomcat1
#进入容器
[root@docker ~]# docker exec -it 804560e03f72 /bin/bash
#发现问题:1、linux命令少了,没有webapps,阿里云默认最小的镜像,已经将没必要的都去掉了,保证最小可运行的环境。
[root@docker ~]# docker pull elasticsearch
[root@docker ~]# docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:latest
1ae3e373b40b78b61e5e85add46c42dd535a2a5594cfba9697ffa79f19db4fe9
Elasticsearch目录结构如下:
bin :脚本文件,包括 ES 启动 & 安装插件等等
config : elasticsearch.yml(ES 配置文件)、jvm.options(JVM 配置文件)、日志配置文件等等
lib : 类库
logs : 日志文件
modules : ES 所有模块,包括 X-pack 等
plugins : ES 已经安装的插件。默认没有插件
data : ES 启动的时候,会有该目录,用来存储文档数据。该目录可以设置
[root@docker ~]# curl localhost:9200
{
"name" : "nGMmHjy",
"cluster_name" : "elasticsearch",
"cluster_uuid" : "JqDooVpbT9GUw1G8SOag5A",
"version" : {
"number" : "5.6.12",
"build_hash" : "cfe3d9f",
"build_date" : "2018-09-10T20:12:43.732Z",
"build_snapshot" : false,
"lucene_version" : "6.6.1"
},
"tagline" : "You Know, for Search"
}
#进入容器修改配置,解决跨域访问问题
docker exec -it elasticsearch /bin/bash
root@ac9cad2ef2f2:/usr/share/elasticsearch/config# vim elasticsearch.yml
#添加在末尾
http.cors.enabled: true
http.cors.allow-origin: "*"
[root@docker ~]# docker restart elasticsearch
[root@docker ~]# docker inspect elasticsearch #查看对应的IP地址
[root@docker ~]# docker pull kibana
[root@docker ~]# docker run -d --name kibana -e ELASTICSEARCH_HOSTS=http://172.18.0.2:9200 -p 5601:5601 -d kibana:latest
root@e1af140ea882:/etc/kibana# vim kibana.yml
docker restart kibana
portainer(先用这个)
docker图形化界面管理工具,提供一个后台面板供我们操作。
docker run -d -p 8088:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer
访问测试:http://本地IP:8088
Rancher(CI/CD再用)
UnionFS(联合文件系统)
联合文件系统( UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。
联合文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。另外,不同 Docker 容器就可以共享一些基础的文件系统层,同时再加上自己独有的改动层,大大提高了存储的效率。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终文件系统会包括所有底层的文件和目录
Docker 目前支持的联合文件系统种类包括 AUFS, btrfs, vfs 和 DeviceMapper。
docker镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统UnionFS。
bootfs(boot file system)主要包含bootloader和kernel,bootloader主要是引导加载kernel,linux刚启动时会加载 bootfs文件系统,在docker镜像的最底层是bootfs,这一层与我们典型的linux/unix系统是一样的,包含boot加载器和内核,当boot加载完成之后整个内核就都在内存中,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs(root file system) 在bootfs之上,包含的就是典型的linux系统中的/dev,/porc,/etc/等标准目录,rootfs就是各种不同的操作系统发行版,比如centos,Ubuntu等等。
docker镜像采用这种分层结构,好处莫过于资源共享,比如有多个镜像都从相同的Base镜像构建而来,那么宿主机只需要在磁盘上保留一份Base镜像,同时内存中也只需要加载一份Base镜像,这样就可以为所有的容器服务,而且镜像的没一层都可以被共享。
查看镜像分层的方式可以通过docker image inspect命令。
[root@docker ~]# docker image inspect redis
[
{
"Id": "sha256:7614ae9453d1d87e740a2056257a6de7135c84037c367e1fffa92ae922784631",
"RepoTags": [
"redis:latest"
],
"RepoDigests": [
"redis@sha256:db485f2e245b5b3329fdc7eff4eb00f913e09d8feb9ca720788059fdc2ed8339"
],
"Parent": "",
"Comment": "",
"Created": "2021-12-21T12:42:49.755107412Z",
"Container": "13d25f53410417c5220c8dfe8bd49f06abdbcd69faa62a9b877de02464bb04a3",
"ContainerConfig": {
"Hostname": "13d25f534104",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"6379/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.12",
"REDIS_VERSION=6.2.6",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.2.6.tar.gz",
"REDIS_DOWNLOAD_SHA=5b2b8b7a50111ef395bf1c1d5be11e6e167ac018125055daa8b5c2317ae131ab"
],
"Cmd": [
"/bin/sh",
"-c",
"#(nop) ",
"CMD [\"redis-server\"]"
],
"Image": "sha256:e093f59d716c95cfce82c676f099b960cc700432ab531388fcedf79932fc81ec",
"Volumes": {
"/data": {}
},
"WorkingDir": "/data",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": {}
},
"DockerVersion": "20.10.7",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"6379/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"GOSU_VERSION=1.12",
"REDIS_VERSION=6.2.6",
"REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.2.6.tar.gz",
"REDIS_DOWNLOAD_SHA=5b2b8b7a50111ef395bf1c1d5be11e6e167ac018125055daa8b5c2317ae131ab"
],
"Cmd": [
"redis-server"
],
"Image": "sha256:e093f59d716c95cfce82c676f099b960cc700432ab531388fcedf79932fc81ec",
"Volumes": {
"/data": {}
},
"WorkingDir": "/data",
"Entrypoint": [
"docker-entrypoint.sh"
],
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "linux",
"Size": 112691373,
"VirtualSize": 112691373,
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/8ab6d8bb3e857055d3923e635bd64a0e799245c02e725074a36ff10402fc81b8/diff:/var/lib/docker/overlay2/53a7d9746e1ec40cd89583afcaca59d8346ceb3a8f42d0a5888a4814ddd33259/diff:/var/lib/docker/overlay2/06cbc301f21651802cde49c292a05119268053dbc58b328971ccbe7842f05462/diff:/var/lib/docker/overlay2/481ac78311e0a3d9f1dc8ee0d42b2ff3df1c0313b5f3ac6532c55d6f58ee0014/diff:/var/lib/docker/overlay2/6f824c4190ec5cb695032064953e090e2bc42f9803d59ecd939fbc6e22210a56/diff",
"MergedDir": "/var/lib/docker/overlay2/3d2c6f23de56940a5e9ab96117baebcda2aab6e6f6cdf1d79f63d7f667ca8664/merged",
"UpperDir": "/var/lib/docker/overlay2/3d2c6f23de56940a5e9ab96117baebcda2aab6e6f6cdf1d79f63d7f667ca8664/diff",
"WorkDir": "/var/lib/docker/overlay2/3d2c6f23de56940a5e9ab96117baebcda2aab6e6f6cdf1d79f63d7f667ca8664/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:2edcec3590a4ec7f40cf0743c15d78fb39d8326bc029073b41ef9727da6c851f",
"sha256:9b24afeb7c2f21e50a686ead025823cd2c6e9730c013ca77ad5f115c079b57cb",
"sha256:4b8e2801e0f956a4220c32e2c8b0a590e6f9bd2420ec65453685246b82766ea1",
"sha256:529cdb636f61e95ab91a62a51526a84fd7314d6aab0d414040796150b4522372",
"sha256:9975392591f2777d6bf4d9919ad1b2c9afa12f9a9b4d260f45025ec3cc9b18ed",
"sha256:8e5669d8329116b8444b9bbb1663dda568ede12d3dbcce950199b582f6e94952"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]
理解:
所有的docker镜像都起始于一个基础镜像层,当进行或增加新的内容时,就会在当前镜像之上,创建一个新的镜像层。举一个简单的例子,基于Ubuntu Linux16.4创建一个新的镜像,这就是新镜像的第一层,如果在该镜像中添加python包,就会在基础镜像层之上创建第二个镜像层,如果继续添加一个安全补丁,就会创建第三个镜像层,该镜像当前已经包含3个镜像层,如下图所示。
特点
docker镜像都是只读,当容器启动时,一个新的可写层被加载到镜像的顶部,这一层就是我们的容器层,容器之下都叫镜像层。
提交一个自己的镜像
docker commit 提交容器成为一个新的副本
docker commit -m=“提交的描述信息” -a="作者" 容器ID 目标镜像名:tag
实战测试
#1、启动一个默认的tomcat
#2、发现这个默认的Tomcat是没有的webapps应用,镜像的原因,官方的镜像默认webapps下是没有目录的,
#3、我自己拷贝进去基本的文件
#4、将我们操作过的容器通过commit提交成一个镜像,
Docker将运用与运行的环境打包形成容器运行, Docker容器产生的数据,如果不通过docker commit生成新的镜像,使得数据做为镜像的一部分保存下来, 那么当容器删除后,数据自然也就没有了。 为了能保存数据在Docker中我们使用卷。|
卷就是目录或文件,存在于一个或多个容器中,由Docker挂载到容器,但卷不属于联合文件系统(Union FileSystem),因此能够绕过联合文件系统提供一些用于持续存储或共享数据的特性:。
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。
Docker容器卷的工作就是将docker容器数据通过映射进行备份+持久化到本地的主机目录
方式一:直接使用命令挂载
[root@docker ~]# docker run -d --name centos -v /home/centos:/home centos:latest
d63cf48430d21ca672cca359c1ca07315187fa759fef578f2e9256ed39d39f72
#通过docker inspect 容器ID 来查看挂载
[root@docker ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mysql latest 3218b38490ce 17 months ago 516MB
#运行容器,做数据挂载
docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:latest
[root@docker ~]# firewall-cmd --add-port=3310/tcp
success
[root@docker ~]# firewall-cmd --add-port=3310/tcp --permanent
success
[root@docker ~]# firewall-cmd --reload
success
[root@docker ~]# netstat -ntulp |grep 3310
tcp 0 0 0.0.0.0:3310 0.0.0.0:* LISTEN 2054/docker-proxy
tcp6 0 0 :::3310 :::* LISTEN 2058/docker-proxy
#启动成功之后,我们在本地使用数据库工具进行连接测试。
MySQL
复制数据流程主库在数据更新提交事务之前,将事件异步记录到binlog二进制日志文件中,日志记录完成后存储引擎提交本次事务
从库启动一个I/O线程与主库建立连接,用来请求主库中要更新的binlog。这时主库创建的binlog dump线程,这是二进制转储线程,如果有新更新的事件,就通知I/O线程;当该线程转储二进制日志完成,没有新的日志时,该线程进入sleep状态。
从库的I/O线程接收到新的事件日志后,保存到自己的relay log(中继日志)中
从库的SQL线程读取中继日志中的事件,并执行更新保存。
在服务器创建两个目录 master 与 slave 分别对应主从数据库的data和conf ,目录结构如下
[root@docker ~]# tree /usr/local/master
/usr/local/master
├── conf
└── data
2 directories, 0 files
[root@docker ~]# tree /usr/local/slave
/usr/local/slave
├── conf
└── data
[root@docker ~]# cat /usr/local/master/conf/my.cnf
[mysqld]
server-id=1024
## 开启二进制日志功能,可以随便取(关键)
log-bin=mysql-bin
secure_file_priv=/var/lib/mysql
default_authentication_plugin=mysql_native_password #设置密码规则
max_connections=1000 #最大连接数设置 根据实际需要 自行调整
[root@docker ~]# cat /usr/local/slave/conf/my.cnf
[mysqld]
## 设置server_id,注意要唯一
server-id=1022
## 开启二进制日志功能,以备Slave作为其它Slave的Master时使用
log-bin=mysql-slave-bin
## relay_log配置中继日志
relay_log=edu-mysql-relay-bin
secure_file_priv=/var/lib/mysql
default_authentication_plugin=mysql_native_password #设置密码规则
max_connections=1000 #最大连接:数
然后利用镜像分别启动两个容器,一个master 一个slave , master端口为3339,slave为3340 对应的root密码为123456,映射配置文件和数据存储目录到mysql
[root@docker ~]# docker run -d --name master -p 3339:3306 -v /usr/local/master/conf/my.cnf:/etc/mysql/my.cnf -v /usr/local/master/data/:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:latest
d64ca628a0330b2c5963ffa013949fd69646d4fc93c1497a2067ddf13e7ab830
[root@docker ~]# docker run -d --name slave -p 3340:3306 -v /usr/local/slave/conf/my.cnf:/etc/mysql/my.cnf -v /usr/local/slave/data/:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:latest
876fd7a8c09672a0578d515775ee5ac50a4fc2669a92b14ced2e0fe9fa5c068c
使用navicat连接 master数据库,进行slave账户创建及相关授权
mysql> create user 'slave'@'%' identified by '123456';
Query OK, 0 rows affected (0.04 sec)
mysql> grant replication slave, replication client on *.* to 'slave'@'%';
Query OK, 0 rows affected (0.01 sec)
#在Master进入mysql,执行show master status;
mysql> use mysql;
Database changed
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000003 | 673 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.03 sec)
#回到linux服务器,执行命令查看master容器的ip地址
[root@docker ~]# docker inspect master --format={{.NetworkSettings.IPAddress}}
172.17.0.4
#然后在slave数据库服务器中根据上面得到的信息执行命令,主要master_log_file和 master_log_pos这两个参数需要根据上面语句查出来的结果进行配置,IP分配的内部地址基本就是172.17.0.2,如果不是这个地址改为 上述 查询出来的地址即可。
mysql> change master to master_host='172.17.0.4', master_user='slave', master_password='123456', master_port=3306, master_log_file='mysql-bin.000003', master_log_pos= 673, master_connect_retry=30;
master_port: Master的端口号,指的是容器的端口号.
master_user:用于数据同步的用户
master_password:用于同步的用户的密码
master_log_file:指定Slave从哪个日志文件开始复制数据,即上文中提到的File字段的值
master_log_pos:从哪个Position开始读,即上文中提到的Position字段的值
master_connect_retry:如果连接失败,重试的时间间隔,单位是秒,默认是60秒
[root@docker ~]# docker exec -it slave mysql -uroot -p123456
mysql> show slave status \G
Slave_IO_Running: No
Slave_SQL_Running: No
可以看到这两个属性都为No
设置从数据库开启主从服务
mysql> start slave;
Query OK, 0 rows affected, 1 warning (0.03 sec)
mysql> show slave status \G
*************************** 1. row ***************************
Slave_IO_State: Waiting for source to send event
Master_Host: 172.17.0.4
Master_User: slave
Master_Port: 3306
Connect_Retry: 30
Master_Log_File: mysql-bin.000003
Read_Master_Log_Pos: 673
Relay_Log_File: edu-mysql-relay-bin.000002
Relay_Log_Pos: 324
Relay_Master_Log_File: mysql-bin.000003
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
所谓匿名挂载(匿名卷),即在进行数据卷挂载的时候不指定宿主机的数据卷目录,-v命令之后直接跟上容器内数据卷所在的路径。
而具名挂载(命名卷)即在进行数据卷挂载的时候既指定宿主机数据卷所在路径,又指定容器数据卷所在路径。
命名卷在用过一次之后以后挂载容器的时候还是可以继续使用,所以一般在需要保存数据的时候使用命名卷的方式,匿名卷则是随着容器的建立而建立,随着容器的关闭而消亡。匿名卷一般用来存储无关痛痒的数据。
#匿名挂载
[root@docker ~]# docker run -d -P --name nginx01 -v /etc/nginx nginx:latest
#查看所有volume的情况
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 0b2969a1a41591c0d8b9b1c5387727487362c5711a60ee92a2ded36f7d48a0a8
#我们在-v只写了容器内的路径,没有写宿主机的路径,这就是匿名挂载。
[root@docker ~]# docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
[root@docker ~]# docker volume ls
local juming-nginx
所有docker容器内的卷,没有指定目录的情况下都是在/var/lib/docker/volumes
我们通过具名挂载可以方便找到我们的一个卷,大多数情况在使用的具名挂载
#如何确定具名挂载还是匿名挂载,还是指定路劲挂载
-v 容器内路径 #匿名挂载
-v 卷名:容器内路径#具名挂载
-v 宿主机路径:容器内路径 #指定路径挂载
拓展:
#通过-v 容器内路径:rw 改变读写权限
#一旦这个设置了容器权限,容器对我们挂载出来的内容有限定了。
docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx
docker run -d -P --name nginx03 -v juming-nginx:/etc/nginx:ro nginx
#通过--volumes-from继承父容器数据卷
[root@docker docker-test-volume]# docker run -it --name docker01 qhz/centos:1.0
[root@94918946324f /]# ls
bin etc lib lost+found mnt proc run srv tmp var volume02
dev home lib64 media opt root sbin sys usr volume01
[root@docker docker-test-volume]# docker run -it --name docker02 --volumes-from docker01 qhz/centos:1.0
[root@a2c49dcb063f /]# ls -l
total 0
lrwxrwxrwx. 1 root root 7 Nov 3 2020 bin -> usr/bin
drwxr-xr-x. 5 root root 360 Jun 15 02:45 dev
drwxr-xr-x. 1 root root 66 Jun 15 02:45 etc
drwxr-xr-x. 2 root root 6 Nov 3 2020 home
lrwxrwxrwx. 1 root root 7 Nov 3 2020 lib -> usr/lib
lrwxrwxrwx. 1 root root 9 Nov 3 2020 lib64 -> usr/lib64
drwx------. 2 root root 6 Sep 15 2021 lost+found
drwxr-xr-x. 2 root root 6 Nov 3 2020 media
drwxr-xr-x. 2 root root 6 Nov 3 2020 mnt
drwxr-xr-x. 2 root root 6 Nov 3 2020 opt
dr-xr-xr-x. 223 root root 0 Jun 15 02:45 proc
dr-xr-x---. 2 root root 162 Sep 15 2021 root
drwxr-xr-x. 11 root root 163 Sep 15 2021 run
lrwxrwxrwx. 1 root root 8 Nov 3 2020 sbin -> usr/sbin
drwxr-xr-x. 2 root root 6 Nov 3 2020 srv
dr-xr-xr-x. 13 root root 0 Jun 15 00:59 sys
drwxrwxrwt. 7 root root 171 Sep 15 2021 tmp
drwxr-xr-x. 12 root root 144 Sep 15 2021 usr
drwxr-xr-x. 20 root root 262 Sep 15 2021 var
drwxr-xr-x. 2 root root 6 Jun 15 02:43 volume01
drwxr-xr-x. 2 root root 6 Jun 15 02:43 volume02
[root@docker docker-test-volume]# docker exec -it docker01 /bin/bash
[root@94918946324f /]# cd volume01
[root@94918946324f volume01]# cd ..
[root@94918946324f /]# touch volume01/docker01
[root@94918946324f /]# exit
exit
[root@docker docker-test-volume]# docker exec -it docker02 /bin/bash
[root@a2c49dcb063f /]# ls volume01
docker01
dockerfile就是用来构建docker镜像的构建文件,命令参数脚本
构建步骤:
1、编写一个dockerfile文件
2、docker build 构建成为一个镜像
3、docker run 运行镜像
4、docker push 发布镜像(DockerHub、阿里云镜像仓库)
基础知识
1、每个保留关键字(指令)都必须是大写字母
2、执行从上到下 依次执行
3、每一个指令都会创建提交一个新的镜像层,并提交。
FROM #基础镜像
MAINTAINER #镜像是谁写的,姓名加邮箱
RUN #镜像构建的时候需要运行的命令
COPY #拷贝dockerfile上下文中本机到容器内
ADD #拷贝dockerfile上下文中本机、远程文件、需要自动解压的文件到容器内
WORKDIR #镜像的工作目录
VOLUME #挂载的目录
EXPOST #保留端口配置
CMD #指定这个容器启动的时候要运行的命令命令
ENTRYPOINT #指定这个容器启动的时候要运行的命令,可以追加
ONBUILD #当构建一个被继承dockerfile这个时候就会运行ONBUILD指令,触发指令
ARG #定义构建参数,可以在构建命令docker build中用--build-<参数名>=<值> 来覆盖
ENV #构建的时候设置环境变量
USER #指定容器启动的用户
HEALTHCHECK #设置检查容器健康状态
SHELL #指定RUN、ENTRYPOINT、CMD指令的shell,linux默认/bin/bash
LABEL #用来给镜像以键值对的形式添加一些元数据(metadata)
FROM [--platform=] [AS ]
FROM [--platform=] [:] [AS ]
FROM [--platform=] [@] [AS ]
FROM 指令初始化一个新的构建阶段,并为后续指令设置基础镜像,该镜像可以是任何有效的镜像。举个例子:
FROM centos:7
...
需要注意的是:
FROM
指令开始;但ARG
是Dockerfile中唯一可能先于FROM
的指令(具体参考下面的ARG介绍)。FROM
可以在一个Dockerfile中出现多次,以创建多个映像或使用一个构建阶段作为另一个构建阶段的依赖项。只需在每个新的FROM
指令之前记录提交的最后一个图像ID输出。每个FROM
指令清除前面指令创建的任何状态。[AS ]
参数可以添加在FROM
指令之后,来为新的构建阶段指定一个名称。该名称可以在后续的FROM
和COPY ——FROM =
指令中使用,以引用在此阶段构建的映像。tag
或digest
值是可选的。如果省略其中任何一个,构造器默认采用latest
标记。如果不能找到tag
值,构造器将返回一个错误。--platform
标志可用于在FROM
引用多平台镜像的情况下指定镜像的平台。如:linux/amd64
, linux/arm64
, or windows/amd64
scratch
。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。MAINTAINER
MAINTAINER
指令设置生成的镜像的Author字段,已经废弃使用。LABEL
指令是一个更灵活的版本,你、应该使用它,因为它可以设置所需要的任何元数据,并且可以很容易地通过docker inspect
查看。可以使用与MAINTAINER
字段相对应的标签:
LABEL org.opencontainers.image.authors="[email protected]"
在下面将会介绍LABEL
指令。
RUN
指令将在当前镜像上的新层中执行任何命令并提交结果。生成的提交镜像将用于Dockerfile中的下一步。其格式有两种:
RUN <命令>
,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN
指令就是这种格式。RUN ["可执行文件", "参数1", "参数2"]
,这更像是函数调用中的格式。需要注意的是,Dockerfile 中每一个指令都会建立一层,RUN
也不例外。每一个 RUN
的行为,就会新建立一层,在其上执行这些命令,执行结束后,commit
这一层的修改,构成新的镜像。
在shell形式中,可以使用(反斜杠)来将单个RUN指令继续到下一行。例如这两行:
RUN /bin/bash -c 'source $HOME/.bashrc; \
echo $HOME'
等价于
RUN /bin/bash -c 'source $HOME/.bashrc; echo $HOME'
COPY [--chown=:] <源路径>... <目标路径>
COPY [--chown=:] ["<源路径1>",... "<目标路径>"]
和 RUN
指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。COPY
指令将从构建上下文目录中 <源路径>
的文件/目录复制到新的一层的镜像内的 <目标路径>
位置。比如:
COPY package.json /usr/src/app/
<源路径>
可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match
规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径>
可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR
指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
--chown=
选项用来改变文件的所属用户及所属组。
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/
注意:如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。
ADD
指令和 COPY
的格式和性质基本一致。但是在 COPY
基础上增加了一些功能。
比如 <源路径>
可以是一个 URL
,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径>
去。下载后的文件权限自动设置为 600
,如果这并不是想要的权限,那么还需要增加额外的一层 RUN
进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN
指令进行解压缩。所以不如直接使用 RUN
指令,然后使用 wget
或者 curl
工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。
如果 <源路径>
为一个 tar
压缩文件的话,压缩格式为 gzip
, bzip2
以及 xz
的情况下,ADD
指令将会自动解压缩这个压缩文件到 <目标路径>
去。
在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu
中:
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...
同样,在使用该指令的时候还可以加上 --chown=
选项来改变文件的所属用户及所属组。
COPY
vs ADD
:
在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY
,因为 COPY
的语义很明确,就是复制文件而已,而 ADD
则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD
的场合,就是所提及的需要自动解压缩的场合。
另外需要注意的是,ADD
指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
因此在 COPY
和 ADD
指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY
指令,仅在需要自动解压缩的场合使用 ADD
。
The CMD
指令的三种格式:
CMD ["executable","param1","param2"] (exec 格式, 首选推荐)
CMD ["param1","param2"] (在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数,下面介绍ENTRYPOINT指令)
CMD command param1 param2 (shell 格式)
Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD
指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu
镜像默认的 CMD
是 /bin/bash
,如果我们直接 docker run -it ubuntu
的话,会直接进入 bash
。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release
。这就是用 cat /etc/os-release
命令替换了默认的 /bin/bash
命令了,输出了系统版本信息。
在指令格式上,一般推荐使用 exec
格式,如果使用 shell
格式的话,实际的命令会被包装为 sh -c
的参数的形式进行执行。比如:
CMD echo $HOME
在实际执行中,会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]
**注意1:**容器中应用应在前台执行;原因:Docker 不是虚拟机,容器就是进程。如下面Nginx容器启动的命令。
无效的命令(容器执行后就立即退出,原因容器主进程结束):
CMD service nginx start
有效命令(前台方式运行,不结束主进程):
CMD ["nginx", "-g", "daemon off;"]
**注意2:**一个Dockerfile应该仅有一个CMD
指令,当存在多个,仅最后一个生效(以最后一个为准)。
ENTRYPOINT
的格式和 RUN
指令格式一样,分为 exec
格式和 shell
格式。
ENTRYPOINT
的目的和 CMD
一样,都是在指定容器启动程序及参数。ENTRYPOINT
在运行时也可以替代,不过比 CMD
要略显繁琐,需要通过 docker run
的参数 --entrypoint
来指定。
当指定了 ENTRYPOINT
后,CMD
的含义就发生了改变,不再是直接的运行其命令,而是将 CMD
的内容作为参数传给 ENTRYPOINT
指令,换句话说实际执行时,将变为:
""
那么有了 CMD
后,为什么还要有 ENTRYPOINT
呢?这种
到底是什么意思,有什么好处?
举个例子:
假设我们需要一个得知自己当前公网 IP 的镜像,那么可以先用 CMD
来实现:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
CMD [ "curl", "-s", "http://myip.ipip.net" ]
使用 docker build -t myip .
来构建镜像。 然后启动容器:
$ docker run myip
当前 IP:219.142.100.68 来自于:中国 北京 北京 电信
从上面的 CMD
中可以看到实质的命令是 curl
,那么如果我们希望显示 HTTP 头信息,就需要加上 -i
参数,结果如下:
$ docker run myip -i
docker: Error response from daemon: invalid header field value "oci runtime error: container_linux.go:247: starting container process caused \"exec: \\\"-i\\\": executable file not found in $PATH\"\n".
显然curl -s http://myip.ipip.net
被替换成了-i
,并不存在这样的命令于是报错了。我们需要执行下面的命令才能正确的执行:
$ docker run myip curl -s http://myip.ipip.net -i
HTTP/1.1 200 OK
...
当前 IP:219.142.100.68 来自于:中国 北京 北京 电信
这里便用完整的 curl -s http://myip.ipip.net -i
替换了curl -s http://myip.ipip.net
从而实现了HTTP头信息的显示。但这显然不是很好的解决方案,而使用 ENTRYPOINT
就可以解决这个问题。现在我们重新用 ENTRYPOINT
来实现这个镜像:
FROM ubuntu:18.04
RUN apt-get update \
&& apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]
这次我们再来尝试直接使用 docker run myip -i
:
$ docker run myip
当前 IP:219.142.100.68 来自于:中国 北京 北京 电信
$ docker run myip -i
HTTP/1.1 200 OK
Date: Wed, 22 Sep 2021 09:18:21 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 69
Connection: keep-alive
X-Shadow-Status: 200
X-Via-JSL: 1d9bd9a,4f293cc,-
Set-Cookie: __jsluid_h=9e709c5148e18188831d1230155ff4d4; max-age=31536000; path=/; HttpOnly
X-Cache: bypass
当前 IP:219.142.100.68 来自于:中国 北京 北京 电信
由此可见,当存在 ENTRYPOINT
后,CMD
的内容将会作为参数传给 ENTRYPOINT
,而这里 -i
就是新的 CMD
,因此会作为参数传给 curl
,从而达到了我们预期的效果。
ENV
指令用于设置环境变量,无论是后面的其它指令,如 RUN
,还是运行时的应用,都可以直接使用这里定义的环境变量。
格式有两种:
ENV
ENV = =...
示例1:
如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。
ENV VERSION=1.0 DEBUG=on \
NAME="Happy Feet"
示例2:
定义环境变量,在后续的指令中使用这个环境变量。比如在官方 node
镜像 Dockerfile
中,就有类似这样的代码。这里先定义了环境变量 NODE_VERSION
,其后的 RUN
这层里,多次使用 $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" \
&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \
&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \
&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \
&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \
&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
可以看到,将来升级镜像构建版本的时候,只需要更新 7.2.0
即可,Dockerfile
构建维护变得更轻松了。
下列指令都支持环境变量: ADD
、COPY
、ENV
、EXPOSE
、FROM
、LABEL
、USER
、WORKDIR
、VOLUME
、STOPSIGNAL
、ONBUILD
、RUN
。
可以看到,环境变量可以使用的地方很多,很强大。通过环境变量,可以让一份 Dockerfile
制作更多的镜像,只需使用不同的环境变量即可。
ARG <参数名>[=<默认值>]
构建参数和 ENV
的效果一样,都是设置环境变量。所不同的是,ARG
所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG
保存密码之类的信息,因为 docker history
还是可以看到所有值的。
Dockerfile
中的 ARG
指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build
中用 --build-arg <参数名>=<值>
来覆盖。
灵活的使用 ARG
指令,能够在不修改 Dockerfile 的情况下,构建出不同的镜像。
ARG 指令有生效范围,如果在 FROM
指令之前指定,那么只能用于 FROM
指令中。
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo ${DOCKER_USERNAME}
使用上述 Dockerfile 会发现无法输出 ${DOCKER_USERNAME}
变量的值,要想正常输出,必须在 FROM
之后再次指定 ARG
# 只在 FROM 中生效
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
# 要想在 FROM 之后使用,必须再次指定
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中(后面进一步介绍 Docker 卷的概念 待完善)。
为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile
中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
VOLUME /data
这里的 /data
目录就会在容器运行时自动挂载为匿名卷,任何向 /data
中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行容器时可以覆盖这个挂载设置。比如:
$ docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata
这个命名卷挂载到了 /data
这个位置,替代了 Dockerfile
中定义的匿名卷的挂载配置。
EXPOSE <端口1> [<端口2>...]
EXPOSE
指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P
时,会自动随机映射 EXPOSE
的端口。
要将 EXPOSE
和在运行时使用 -p <宿主端口>:<容器端口>
区分开来。-p
,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE
仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
WORKDIR <工作目录路径>
使用 WORKDIR
指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR
会帮你建立目录。
示例1
Dockerfile
进行构建镜像运行后,找不到 /app/world.txt
文件的写法:
RUN cd /app
RUN echo "hello" > world.txt
在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile
中,这两行 RUN
命令的执行环境根本不同,是两个完全不同的容器。
正确找到 /app/world.txt
文件的写法:
WORKDIR /app
RUN echo "hello" > world.txt
示例2
WORKDIR
指令使用的相对路径,那么所切换的路径与之前的 WORKDIR
有关:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
RUN pwd
的工作目录为 /a/b/c
。
USER <用户名>[:<用户组>]
USER
指令和 WORKDIR
相似,都是改变环境状态并影响以后的层。WORKDIR
是改变工作目录,USER
则是改变之后层的执行 RUN
, CMD
以及 ENTRYPOINT
这类命令的身份。
注意,USER
只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
如果以 root
执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su
或者 sudo
,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu
。
# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \
&& chmod +x /usr/local/bin/gosu \
&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]
格式:
HEALTHCHECK [选项] CMD <命令>
:设置检查容器健康状况的命令HEALTHCHECK NONE
:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令HEALTHCHECK
指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。
当在一个镜像指定了 HEALTHCHECK
指令后,用其启动容器,初始状态会为 starting
,在 HEALTHCHECK
指令检查成功后变为 healthy
,如果连续一定次数失败,则会变为 unhealthy
。
HEALTHCHECK
支持下列选项:
--interval=<间隔>
:两次健康检查的间隔,默认为 30 秒;--timeout=<时长>
:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;--retries=<次数>
:当连续失败指定次数后,则将容器状态视为 unhealthy
,默认 3 次。和 CMD
, ENTRYPOINT
一样,HEALTHCHECK
只可以出现一次,如果写了多个,只有最后一个生效。
在 HEALTHCHECK [选项] CMD
后面的命令,格式和 ENTRYPOINT
一样,分为 shell
格式,和 exec
格式。命令的返回值决定了该次健康检查的成功与否:0
:成功;1
:失败;2
:保留,不要使用这个值。
举个例子:
假设有个镜像是个最简单的 Web 服务,通过增加健康检查来判断其 Web 服务是否在正常工作,可以用 curl
来帮助判断,其 Dockerfile
的 HEALTHCHECK
可以这么写:
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=5s --timeout=3s \
CMD curl -fs http://localhost/ || exit 1
这里我们设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1
作为健康检查命令。
使用 docker build
来构建这个镜像:
$ docker build -t myweb:v1 .
构建好了后,我们启动一个容器:
$ docker run -d --name web -p 80:80 myweb:v1
当运行该镜像后,可以通过 docker container ls
看到最初的状态为 (health: starting)
:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 3 seconds ago Up 2 seconds (health: starting) 80/tcp, 443/tcp web
在等待几秒钟后,再次 docker container ls
,就会看到健康状态变化为了 (healthy)
:
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03e28eb00bd0 myweb:v1 "nginx -g 'daemon off" 18 seconds ago Up 16 seconds (healthy) 80/tcp, 443/tcp web
如果健康检查连续失败超过了重试次数,状态就会变为 (unhealthy)
。
为了帮助排障,健康检查命令的输出(包括 stdout
以及 stderr
)都会被存储于健康状态里,可以用 docker inspect
来查看。
$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
"FailingStreak": 0,
"Log": [
{
"End": "2016-11-25T14:35:37.940957051Z",
"ExitCode": 0,
"Output": "\n\n\nWelcome to nginx! \n\n\n\nWelcome to nginx!
\nIf you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.
\n\nFor online documentation and support please refer to\n\"
http://nginx.org/\">nginx.org.
\nCommercial support is available at\n\"http://nginx.com/\">nginx.com.\n\nThank you for using nginx.
\n\n\n",
"Start": "2016-11-25T14:35:37.780192565Z"
}
],
"Status": "healthy"
}
格式:ONBUILD <其它指令>
。
ONBUILD
是一个特殊的指令,它后面跟的是其它指令,比如 RUN
, COPY
等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行。
Dockerfile
中的其它指令都是为了定制当前镜像而准备的,唯有 ONBUILD
是为了帮助别人定制自己而准备的。
举个例子:
假设我们要制作 Node.js 所写的应用的镜像。我们都知道 Node.js 使用 npm
进行包管理,所有依赖、配置、启动信息等会放到 package.json
文件里。在拿到程序代码后,需要先进行 npm install
才可以获得所有需要的依赖。然后就可以通过 npm start
来启动应用。我们可以先做一个基础镜像:
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]
然后在多个子项目中:
FROM my-node
是的,只有这么一行。当在各个项目目录中,用这个只有一行的 Dockerfile
构建镜像时,之前基础镜像的那三行 ONBUILD
就会开始执行,成功的将当前项目的代码复制进镜像、并且针对本项目执行 npm install
,生成应用镜像。
LABEL
指令用来给镜像以键值对的形式添加一些元数据(metadata)。
LABEL = = = ...
我们还可以用一些标签来申明镜像的作者、文档地址等:
LABEL org.opencontainers.image.authors="yeasy"
LABEL org.opencontainers.image.documentation="https://yeasy.gitbooks.io"
具体可以参考 https://github.com/opencontainers/image-spec/blob/master/annotations.md
STOPSIGNAL signal
官方文档
The
STOPSIGNAL
instruction sets the system call signal that will be sent to the container to exit. This signal can be a valid unsigned number that matches a position in the kernel’s syscall table, for instance 9, or a signal name in the format SIGNAME, for instance SIGKILL.
STOPSIGNAL
指令设置将发送到容器的系统调用信号以退出。此信号可以是与内核的系统调用表中的位置匹配的有效无符号数,例如 9,或 SIGNAME
格式的信号名,例如 SIGKILL
。
默认的stop-signal是SIGTERM,在docker stop的时候会给容器内PID为1的进程发送这个signal,通过–stop-signal可以设置自己需要的signal,主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s。
格式:SHELL ["executable", "parameters"]
SHELL` 指令可以指定 `RUN` `ENTRYPOINT` `CMD` 指令的 shell,Linux 中默认为 `["/bin/sh", "-c"]
SHELL ["/bin/sh", "-c"]
RUN lll ; ls
SHELL ["/bin/sh", "-cex"]
RUN lll ; ls
两个 RUN
运行同一命令,第二个 RUN
运行的命令会打印出每条命令并当遇到错误时退出。
当 ENTRYPOINT
CMD
以 shell 格式指定时,SHELL
指令所指定的 shell 也会成为这两个指令的 shell
SHELL ["/bin/sh", "-cex"]
# /bin/sh -cex "nginx"
ENTRYPOINT nginx
SHELL ["/bin/sh", "-cex"]
# /bin/sh -cex "nginx"
CMD nginx
1、准备镜像文件tomcat压缩包,jdk的压缩包
2、编写dockerfile文件,官方命名Dockerfile。
FROM rockylinux
ADD jdk-8u371-linux-x64.tar.gz /usr/local/
ADD apache-tomcat-8.5.90.tar.gz /usr/local/
RUN yum makecache
RUN yum -y install vim
ENV MYPATH /usr/local
WORKDIR $MYPATH
ENV JAVA_HOME /usr/local/jdk-8
ENV CLASSPATH $JAVA_HOME/LIB/DT.JAR:$JAVA_HOME/lib/tools.jar
ENV CATALINA_HOME /usr/local/apache-tomcat-8.5.90
ENV CATALINA_BASH /usr/local/apache-tomcat-8.5.90
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$$CATALINA_HOME/bin
[root@docker tomcat]# docker build -t diytomcat .
[root@docker tomcat]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
diytomcat latest 140d888f2a27 11 seconds ago 631MB
[root@docker tomcat]# docker run -d --name jdktomcat -p 9090:8080 -v /usr/local/tomcat/test:/usr/local/apache-tomcat-8.5.90/webapps/test -v /usr/local/tomcat/tomcatlogs/:/usr/local/apache-tomcat-8.5.90/logs diytomcat
2e7fecb63e0844828edae7b2a4decc08b212fb205069a7267c73bdcbcc7f2db1
原理
1、我们每启动一个docker容器,docker0就会给docker容器分配一个IP,我们只要安装了docker,就会有有一个docker0桥接模式,使用的技术是evth-pair技术。
#我们发现这个容器带来网卡,都是一对对的
#evth-pair就是一对的虚拟设备接口,他们都是成对出现的,一段连着协议,一段彼此相连
#正因为有这个特性,evth-pair充当一个桥梁,连接各种虚拟网络设备
2、测试下tomcat01和tomcat02是否可以ping通
[root@docker ~]# docker exec -it tomcat02 ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: icmp_seq=0 ttl=64 time=0.150 ms
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.090 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.243 ms
^C--- 172.17.0.2 ping statistics ---
#容器跟容器之间可以互相通信的
结论:tomcat01和tomcat02是公用的一个路由器,docker0
所有的容器不指定网络的情况下,都是docker0路由的,docker会给我们的容器分配一个默认的可用IP
思考一个场景,我们编写了一个微服务,database url=IP:项目不重启,数据库IP换掉了,我们希望可以处理这个问题,可以用名字进行连接。
[root@docker ~]# docker exec -it tomcat01 ping tomcat02
ping: unknown host
#通过--link解决网络连通问题
[root@docker ~]# docker run -d -P --name tomcat03 --link tomcat02 diytomcat
ed0d2020a28dae0de4aa3a82e9ef03c2ed88530b544e2aa23802f9fe6e8a01fb
[root@docker ~]# docker exec -it tomcat03 ping tomcat02
PING tomcat02 (172.17.0.3): 56 data bytes
64 bytes from 172.17.0.3: icmp_seq=0 ttl=64 time=0.163 ms
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.086 ms
64 bytes from 172.17.0.3: icmp_seq=2 ttl=64 time=0.105 ms
#这种情况只用于正向连通,tomcat02ping不通tomcat03
探究inspect
其实tomcat03通过link能通tomcat02的原因是在本地配置hosts文件
现在玩docker已经不建议使用–link了!
自定义网络!不适用docker0,docker0问题不支持容器名进行连接访问
#查看所有网络
[root@docker ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
10fef7b25389 bridge bridge local
b4e7fd1622b1 es-net bridge local
12a4d9979949 host host local
5e377ec5b96c none null local
网络模式
bridge:桥接docker(默认)
none:不配置网络
host:和宿主机共享网络
container: 容器网络连通(用的少!局限很大)
测试
#我们直接启动的命令 --net bridge,而这个就是我们的docker0
docker run -d -P --name tomcat01 diytomcat
docker run -d -P --name tomcat01 --net bridge diytomcat
#docker0特点,默认,域名不能访问,--link 可以打通连接
#我们可以自定义一个网络
[root@docker ~]# docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
5a585c19e175046b06ea59abdbd70cd79c57c6d9291b48af9b5dae399237008c
[root@docker ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
10fef7b25389 bridge bridge local
b4e7fd1622b1 es-net bridge local
12a4d9979949 host host local
5a585c19e175 mynet bridge local
5e377ec5b96c none null local
[root@docker ~]# docker run -d --name tomcat01-mynet -P --net mynet diytomcat
75997a69b8589d47f706dc6ba773055c729ee879ea39cc2c904cfd0192fa9430
[root@docker ~]# docker run -d --name tomcat02-mynet -P --net mynet diytomcat
7aa7b07eca53d7281d54efddc8e4f614d34d735053826cc487337c5f868d493e
[root@docker ~]# docker network inspect mynet
[
{
"Name": "mynet",
"Id": "e73ae983130e22865c49ef8857dbfa078f0f4d5b2730f44486119033841c250d",
"Created": "2023-06-28T14:55:32.528485841+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": {
"75997a69b8589d47f706dc6ba773055c729ee879ea39cc2c904cfd0192fa9430": {
"Name": "tomcat01-mynet",
"EndpointID": "feb98f5ddb9a2b6b10cd562ab8e949b5fe4074b51b41bbf1025c8fb39ef130fb",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
},
"7aa7b07eca53d7281d54efddc8e4f614d34d735053826cc487337c5f868d493e": {
"Name": "tomcat02-mynet",
"EndpointID": "6573beab5a0d47df49ec628ac82606f352d17246d106a06cd5eccd2be3a56a61",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {}
}
]
#再次测试ping连接
[root@docker ~]# docker exec -it tomcat01-mynet ping tomcat02-mynet
PING tomcat02 (192.168.0.3): 56 data bytes
64 bytes from 192.168.0.3: icmp_seq=0 ttl=64 time=0.353 ms
[root@docker ~]# docker exec -it tomcat01-mynet ping 192.168.0.3
PING 192.168.0.3 (192.168.0.3): 56 data bytes
64 bytes from 192.168.0.3: icmp_seq=0 ttl=64 time=0.206 ms
#现在不使用--link也可以ping名字了
我们自定义的网络docker都已经帮我们维护好对应的关系,推荐我们平时使用这样的网络
好处:不同的集群使用不同的网络,保证集群是安全和健康的
#不同网络之间的容器是不通的
[root@docker ~]# docker run -d --name tomcat01 -P diytomcat
20092072fb4b73aac48d6f66f1aeeb6e74e3e0b8936acc335d9bc7d274f71848
[root@docker ~]# docker run -d --name tomcat02 -P diytomcat
60e3ba90ff3e3a1f83bc8def34ed1df5ba92d130db26fcb000ecfa54e2d15080
[root@docker ~]# docker exec -it tomcat01 ping tomcat01-mynet
ping: unknown host
通过connect打通
[root@docker ~]# docker network connect mynet tomcat01
#连通之后就是将tomcat01放到了mynet网络下
#一个容器两个IP地址
#创建网卡
[root@docker ~]# docker network create --driver bridge redis --subnet 172.38.0.0/24 --gateway 172.38.0.1
[root@docker ~]# vim redis
#!/bin/bash
for port in $(seq 1 6)
do
mkdir -p /usr/local/redis/node-$port/conf
touch /usr/local/redis/node-$port/conf/redis.conf
echo -e "port 6379 \nbind 0.0.0.0 \ncluster-enabled yes \ncluster-config-file nodes.conf \ncluster-node-timeout 5000 \ncluster-announce-ip 172.38.0.1$port \ncluster-announce-port 6379 \ncluster-announce-bus-port 16379 \nappendonly yes" > "/usr/local/redis/node-$port/conf/redis.conf"
docker run -p "637$port:6379" -p "1637$port:16379" --name "redis-$port" -v "/usr/local/redis/node-$port/data":"/data" -v "/usr/local/redis/node-$port/conf/redis.conf":"/etc/redis/redis.conf" -d --net redis --ip "172.38.0.1$port" redis:latest redis-server "/etc/redis/redis.conf"
done
[root@docker ~]# ./redis
[root@docker ~]# docker exec -it redis-1 /bin/sh
# redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 172.38.0.15:6379 to 172.38.0.11:6379
Adding replica 172.38.0.16:6379 to 172.38.0.12:6379
Adding replica 172.38.0.14:6379 to 172.38.0.13:6379
# redis-cli -c
127.0.0.1:6379> cluster nodes
23aaf1fdfc852f1f3685bbecc9850013ff05cce3 172.38.0.16:6379@16379 slave 3f722d224432e71bcf74914baf7b1656fefbb1ac 0 1688003083000 2 connected
8ef43913cb4a0f491742604317d35331b13c725c 172.38.0.14:6379@16379 slave 76672a243de074081b10af9407d63d58485fc738 0 1688003082250 3 connected
562f5baf78dcde185c9c44941afa67764dbb2bce 172.38.0.11:6379@16379 myself,master - 0 1688003081000 1 connected 0-5460
3f722d224432e71bcf74914baf7b1656fefbb1ac 172.38.0.12:6379@16379 master - 0 1688003083274 2 connected 5461-10922
76672a243de074081b10af9407d63d58485fc738 172.38.0.13:6379@16379 master - 0 1688003082763 3 connected 10923-16383
4d86d42f0f2efbf8f25baf28a41c234a95f08b5a 172.38.0.15:6379@16379 slave 562f5baf78dcde185c9c44941afa67764dbb2bce 0 1688003082000 1 connected