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 包括三个基本概念:

  • 镜像(Image) :Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。
  • 容器(Container) :镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
  • 仓库(Repository) :仓库可看成一个代码控制中心,用来保存镜像。

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安装

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"]
systemctl daemon-reload
systemctl restart docker

docker为什么比VM快?
1、docker有着比虚拟机更少的抽象层

2、docker利用的是宿主机的内核,VM需要是Guest OS,

所以说新建一个容器的时候,docker不需要像虚拟机一样重新加载一个操作系统内核,避免引导,虚拟机是加载Guest OS,分钟级别的,而docker是利用宿主机的操作系统, 跳过了这个复杂过程,秒级。

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
查看容器中进程信息ps
docker top 容器ID
[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": [
            "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逻辑卷,实现自动同步

docker安装程序

docker中安装Nginx

[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>
<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>
<h1>Welcome to nginx!</h1>
端口暴露概念

docker安装 tomcat

[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,阿里云默认最小的镜像,已经将没必要的都去掉了,保证最小可运行的环境。

docker部署ES+kibana

[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再用)

docker镜像讲解

docker镜像加载原理

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镜像都是只读,当容器启动时,一个新的可写层被加载到镜像的顶部,这一层就是我们的容器层,容器之下都叫镜像层。

提交一个自己的镜像

commit镜像

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 来查看挂载

实战mysql与主从复制

[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复制数据流程
  1. 主库在数据更新提交事务之前,将事件异步记录到binlog二进制日志文件中,日志记录完成后存储引擎提交本次事务

  2. 从库启动一个I/O线程与主库建立连接,用来请求主库中要更新的binlog。这时主库创建的binlog dump线程,这是二进制转储线程,如果有新更新的事件,就通知I/O线程;当该线程转储二进制日志完成,没有新的日志时,该线程进入sleep状态。

  3. 从库的I/O线程接收到新的事件日志后,保存到自己的relay log(中继日志)中

  4. 从库的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
[root@docker docker-test-volume]# docker exec -it docker02 /bin/bash
[root@a2c49dcb063f /]# ls volume01
docker01

dockerfile

dockerfile介绍

dockerfile就是用来构建docker镜像的构建文件,命令参数脚本

构建步骤:

1、编写一个dockerfile文件

2、docker build 构建成为一个镜像

3、docker run 运行镜像

4、docker push 发布镜像(DockerHub、阿里云镜像仓库)

dockerfile构建过程

1、每个保留关键字(指令)都必须是大写字母

2、执行从上到下 依次执行

3、每一个指令都会创建提交一个新的镜像层,并提交。

dockerfile指令

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=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

FROM 指令初始化一个新的构建阶段,并为后续指令设置基础镜像,该镜像可以是任何有效的镜像。举个例子:

FROM centos:7

需要注意的是:

  • 一般来说,Dockerfile都是以FROM指令开始;但ARG是Dockerfile中唯一可能先于FROM的指令(具体参考下面的ARG介绍)。
  • FROM可以在一个Dockerfile中出现多次,以创建多个映像或使用一个构建阶段作为另一个构建阶段的依赖项。只需在每个新的FROM指令之前记录提交的最后一个图像ID输出。每个FROM指令清除前面指令创建的任何状态。
  • [AS <name>] 参数可以添加在FROM指令之后,来为新的构建阶段指定一个名称。该名称可以在后续的FROMCOPY ——FROM =<name>指令中使用,以引用在此阶段构建的映像。
  • tagdigest值是可选的。如果省略其中任何一个,构造器默认采用latest标记。如果不能找到tag值,构造器将返回一个错误。
  • --platform标志可用于在FROM引用多平台镜像的情况下指定镜像的平台。如:linux/amd64, linux/arm64, or windows/amd64
  • 除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
MAINTAINER
MAINTAINER <name>

MAINTAINER指令设置生成的镜像的Author字段,已经废弃使用。LABEL指令是一个更灵活的版本,你、应该使用它,因为它可以设置所需要的任何元数据,并且可以很容易地通过docker inspect查看。可以使用与MAINTAINER字段相对应的标签:

LABEL org.opencontainers.image.authors="SvenDowideit@home.org.au"

在下面将会介绍LABEL指令。

RUN指令将在当前镜像上的新层中执行任何命令并提交结果。生成的提交镜像将用于Dockerfile中的下一步。其格式有两种:

  • shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
  • exec 格式: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=<user>:<group>] <源路径>... <目标路径> 
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如:

COPY package.json /usr/src/app/

<源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则,如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。

--chown=<user>:<group> 选项用来改变文件的所属用户及所属组。

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=<user>:<group> 选项来改变文件的所属用户及所属组。

COPY vs ADD

在 Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。

另外需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

因此在 COPYADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 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

ENTRYPOINT 的格式和 RUN 指令格式一样,分为 exec 格式和 shell 格式。

ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数。ENTRYPOINT 在运行时也可以替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,换句话说实际执行时,将变为:

<ENTRYPOINT> "<CMD>"

那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?这种 <ENTRYPOINT> "<CMD>" 到底是什么意思,有什么好处?

举个例子🌰:

假设我们需要一个得知自己当前公网 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 <key> <value>
ENV <key1>=<value1> <key2>=<value2>...

如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。

ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

定义环境变量,在后续的指令中使用这个环境变量。比如在官方 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 构建维护变得更轻松了。

下列指令都支持环境变量: ADDCOPYENVEXPOSEFROMLABELUSERWORKDIRVOLUMESTOPSIGNALONBUILDRUN

可以看到,环境变量可以使用的地方很多,很强大。通过环境变量,可以让一份 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

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中(后面进一步介绍 Docker 卷的概念 待完善)

为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME /data

这里的 /data 目录就会在容器运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行容器时可以覆盖这个挂载设置。比如:

$ docker run -d -v mydata:/data xxxx

在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。

EXPOSE
EXPOSE <端口1> [<端口2>...]

EXPOSE 指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。

WORKDIR
WORKDIR <工作目录路径>

使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。

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

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
  • 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 来帮助判断,其 DockerfileHEALTHCHECK 可以这么写:

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": "<!DOCTYPE html>\n<html>\n<head>\n<title>Welcome to nginx!</title>\n<style>\n    body {\n        width: 35em;\n        margin: 0 auto;\n        font-family: Tahoma, Verdana, Arial, sans-serif;\n    }\n</style>\n</head>\n<body>\n<h1>Welcome to nginx!</h1>\n<p>If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.</p>\n\n<p>For online documentation and support please refer to\n<a href=\"http://nginx.org/\">nginx.org</a>.<br/>\nCommercial support is available at\n<a href=\"http://nginx.com/\">nginx.com</a>.</p>\n\n<p><em>Thank you for using nginx.</em></p>\n</body>\n</html>\n",
            "Start": "2016-11-25T14:35:37.780192565Z"
    "Status": "healthy"
ONBUILD

格式: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

LABEL 指令用来给镜像以键值对的形式添加一些元数据(metadata)。

LABEL <key>=<value> <key>=<value> <key>=<value> ...

我们还可以用一些标签来申明镜像的作者、文档地址等:

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
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

格式: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

实战测试:tomcat镜像

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

docker网络

理解docker0

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

–link

思考一个场景,我们编写了一个微服务,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地址

实战:部署redis集群

shell脚本
在这里插入图片描述

#创建网卡
[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)
        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"
[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
                    dockerfile就是用来构建docker镜像的构建文件,命令参数脚本构建步骤:1、编写一个dockerfile文件2、docker build 构建成为一个镜像3、docker run 运行镜像4、docker push 发布镜像(DockerHub、阿里云镜像仓库)#查看所有网络 [ root@docker ~ ] # docker network ls NETWORK ID NAME DRIVER SCOPE网络模式bridge:桥接docker(默认)none:不配置网络。
				
文章目录docker简介docker是什么为什么要使用docker核心概念镜像容器仓库Docker命令镜像获取镜像:docker pull查看镜像信息列出所有镜像:docker images添加镜像标签:docker tag查看镜像详细信息:docker inspect查看镜像历史:docker history搜寻镜像:docker search删除镜像:docker rm清理镜像:docker prune上传镜像:docker push帮助指令:docker image help容器创建容器 docker create启动容器 docker start查看容器 docker ps新建并启动容
如官方文档所说,docker是一个自动将应用打包成轻量可移植自包涵的容器的引擎。开发者构建的应用可以一次构建全平台运行,包括本地开发机,生产环境,虚拟机和云等。目前处于开发阶段,不可用于生产环境。在你启动一条命令docker会调用lcx等其他一个组建为这条命令构建一个container,包含了进程运行的所有资源。但是官方文档以说明,docker处于开发阶段目前还不能用于生产环境。 Go语言编写 基于lxc的进程级隔离,而lxc基于cgroup,轻量级 通过cgroup做到文件系统,网络和资源的隔离 使用aufs文件系统存储,写时复制,相同数据只保存一份,节省空间 源机制,可相互分 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上。 Docker是一个重新定义了程序开发测试、交付和部署过程的开放平台,Docker则可以称为构建一次,到处运行,这就是docker提出的“Build once,Run anywhere” 创建镜像的方法有三种: 基于已有的容器创建 基于本地模板导入 基于dockerfile 基于已有的容器创建 主要使用docker commit 命令命令格式: docker commit [OPTIONS] CONTAINER [RE
Docker基本命令Docker基础指令Docker镜像常用命令查看镜像下载镜像删除镜像Docker 容器常用命令1、Docker创建并启动容器2、Docker列出容器3、Docker退出容器4、Docker进入容器5、Docker启动容器6、Docker重启容器7、Docker停止容器8、Docker删除容器 Docker中有两个非常核心的概念? 就好比一个个软件、项目等等,用Java做比的话,那么它就是类的概念; 就好比一个具体的软件、项目,安装到了一个Linux服务器上,因为一个软件或者项目(jdk),可以在多个Linux服务器上安装,那么相当于一个jdk复制了多份放到了不同
版本说明:本文中docker版本主要基于1.10版本,操作系统为centos7。devicemapper在文中缩写为dm。 某个用户的容器启动不起来,启动时候一直报错。通过docker log查看日志,可以看到报错信息如下 Timestamp: 2019-04-01 16:19:26.33690413 +0800 CST Code: System error Message: ca... Device Mapper 是基于内核的框架,支持 Linux 上的许多高级卷管理技术。 Docker 的 devicemapper 存储驱动程序利用此框架的精简配置和快照功能进行镜像和容器管理。本文将 Device Mapper 存储驱动程序称为 devicemapper,将内核框架称为 Device Mapper。 在支持这个特性的系统中,对 devicemapper 的支持包含... 恭喜您写了第三篇博客!标题“容器内没有基础命令解决方案”非常引人注目。您在这篇博客中提到了一个重要问题,并提供了解决方案。非常感谢您分享这样有价值的内容。 对于下一步的创作建议,我谦虚地建议您可以考虑深入探讨一些实际案例,阐述在容器内没有基础命令的情况下,如何应对和解决各种挑战。您可以分享一些技巧或者技术示例,帮助读者更好地理解和应用您提供的解决方案。期待您未来更多的创作! CSDN 正在通过评论红包奖励优秀博客,请看红包流:https://bbs.csdn.net/?type=4&header=0&utm_source=csdn_ai_ada_blog_reply3
docker中update失败 CSDN-Ada助手: 很棒的博客,感谢分享!对于docker的使用,更新是一个非常重要的环节,谢谢你分享了解决update失败的方法。除此之外,作为docker的使用者,了解如何构建和管理容器、镜像的方法也是非常有用的。同时,掌握docker-compose的使用也能帮助你更好地管理多个容器。期待你能分享更多的docker使用经验和技巧。 如何写出更高质量的博客,请看该博主的分享:https://blog.csdn.net/lmy_520/article/details/128686434?utm_source=csdn_ai_ada_blog_reply2 如果您持续创作,完成第三篇博客,并且质量分达到 80 分以上,在评论区就有机会获得红包奖励哦! MySQL8的密码安全策略 CSDN-Ada助手: 推荐 MySQL入门 技能树:https://edu.csdn.net/skill/mysql?utm_source=AI_act_mysql MySQL8的密码安全策略 CSDN-Ada助手: 恭喜你这篇博客进入【CSDN每天最佳新人】榜单,全部的排名请看 https://bbs.csdn.net/topics/615543795。