pdf版本笔记的下载地址: Docker学习笔记01(访问密码:3834)
Docker的安装
Docker的基本组成
首先要明确镜像(image),容器(container)和仓库(repository)的概念:
-
镜像(image): Docker镜像(image)就是一个只读的模板,用于创建 Docker 容器.
Docker镜像和容器之间的关系可以类比为Java中类和对象的关系,一个镜像可以创建很多容器.
-
容器(container): Docker容器(container)容器是用镜像创建的运行实例.它可以被启动,开始,停止,删除.每个容器之间是相互隔离的.可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等)以及运行在其中的应用程序。
-
仓库(repository): 仓库(repository)是集中存放镜像文件的场所,可以分为为公开仓库(public)和私有仓库(private)两种形式.最大的公开仓库是DockerHub.
Docker的安装
理论上Docker只能安装在Linux环境下,Windows系统和Macos系统的Docker本质上是安装了一个Linux虚拟机,因此本教程演示在Centos上安装Docker,在其它Linux发行版上安装Docker的方式大同小异,见Install Docker Engine | Docker Documentation
亲测使用(Windows Subsystem for Linux)的Ubuntu系统是安装不上Docker的.
演示系统环境
目前Docker要求CentOs版本在运CentOS 7以上,系统为64位,系统内核版本为 3.10 以上.使用下列命令可以查看当前操作系统的内核和版本信息:
-
使用
uname -r
命令查看当前操作系统的内核版本号,输出如下:3.10.0-1127.19.1.el7.x86_64
-
使用
cat /etc/os-release
命令查看版本信息,输出如下:NAME="CentOS Linux" VERSION="7 (Core)" ID="centos" ID_LIKE="rhel fedora" VERSION_ID="7" PRETTY_NAME="CentOS Linux 7 (Core)" ANSI_COLOR="0;31" CPE_NAME="cpe:/o:centos:centos:7" HOME_URL="https://www.centos.org/" BUG_REPORT_URL="https://bugs.centos.org/" CENTOS_MANTISBT_PROJECT="CentOS-7" CENTOS_MANTISBT_PROJECT_VERSION="7" REDHAT_SUPPORT_PRODUCT="centos" REDHAT_SUPPORT_PRODUCT_VERSION="7"
安装步骤
确认系统环境满足条件后进行安装,安装步骤如下:
-
安装gcc相关环境
sudo yum -y install gcc sudo yum -y install gcc-c++
-
卸载旧版本Docker
sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine
-
设置yum仓库
yum install -y yum-utils # Docker官方yum仓库,国内有可能访问不上 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 如果访问不上,就运行下面备注是的这一句,设置的是阿里云镜像Docker的yum仓库 # yum-config-manager --add-repo http://mirrors.aliyun.com/dockerce/linux/centos/docker-ce.repo
-
更新yum软件包索引
yum makecache fast
-
安装Docker CE
yum install docker-ce docker-ce-cli containerd.io
-
启动Docker
systemctl start docker
-
测试命令
运行
docker version
输出如下Client: Docker Engine - Community Version: 20.10.3 API version: 1.41 Go version: go1.13.15 Git commit: 48d30b5 Built: Fri Jan 29 14:34:14 2021 OS/Arch: linux/amd64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.3 API version: 1.41 (minimum version 1.12) Go version: go1.13.15 Git commit: 46229ca Built: Fri Jan 29 14:32:37 2021 OS/Arch: linux/amd64 Experimental: false containerd: Version: 1.4.3 GitCommit: 269548fa27e0089a8b8278fc4fc781d7f65a939b runc: Version: 1.0.0-rc92 GitCommit: ff819c7e9184c13b7c2607fe6c30ae19403a7aff docker-init: Version: 0.19.0 GitCommit: de40ad0
运行
docker run hello-world
输出如下:Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/
运行
docker images
输出如下:REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest bf756fb1ae65 13 months ago 13.3kB
上述命令都正常执行不报错,说明Docker安装成功
-
(如果有需要的话)卸载Docker的命令:
systemctl stop docker yum -y remove docker-ce docker-ce-cli containerd.io rm -rf /var/lib/docker
配置阿里云镜像加速服务
安装好Docker后,默认是从DockerHub上下载镜像的,对于国内用户来说,下载会很慢(真的很慢),因此需要国内的容器镜像服务,配置容器镜像服务的步骤如下:
-
注册阿里云账号,进入容器镜像服务,获得自己的镜像加速器地址,比如我的镜像加速器地址就是
https://2mpejt6c.mirror.aliyuncs.com
-
运行下面命令,配置镜像加速器:
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://2mpejt6c.mirror.aliyuncs.com"] #换成你自己的镜像加速器地址 } EOF sudo systemctl daemon-reload sudo systemctl restart docker
这样镜像加速器就配置好了
运行 HelloWorld的过程中发生了什么
运行docker run hello-world
可以下载并启动一个hello-world
容器,其中的运行过程如下:
Docker与VM的比较
为什么Docker比较VM快
- Docker有着比虚拟机更少的抽象层,运行在Docker容器上的程序直接使用的都是实际物理机的硬件资源.
- Docker利用的是宿主机的内核,而不需要Guest OS.
虚拟机(VM) | 容器(Dcoker) | |
---|---|---|
占用磁盘空间 | 非常大,GB级 | 小,MB甚至KB级 |
启动速度 | 慢,分钟级 | 快,秒级 |
运行形态 | 运行于Hypervisor上 | 直接运行在宿主机内核上 |
并发性 | 一台宿主机上十几个,最对几十个 | 上百个,甚至数千个 |
性能 | 逊于宿主机 | 接近宿主机本地进程 |
资源利用率 | 低 | 高 |
Docker命令
常用Dcoker命令
帮助命令
docker version # 显示 Docker 版本信息
docker info # 显示 Docker 系统信息,包括镜像和容器数
docker --help # 帮助
镜像命令
-
docker images
: 列出本地主机上的镜像,运行该命令得到一个表格REPOSITORY TAG IMAGE ID CREATED SIZE hello-world latest bf756fb1ae65 13 months ago 13.3kB
表中各项的意义:
项 意义 REPOSITORY
镜像的仓库源 TAG
镜像的标签 IMAGE ID
镜像的ID CREATED
镜像创建时间 SIZE
镜像大小 常用参数:
参数 意义 -a
列出所有镜像 -q
静默模式,只显示镜像id --digests
显示镜像的摘要信息 -
docker search
: 本质上与在Docker Hub仓库中搜索镜像是一致的,不常用. -
docker pull 镜像名[:tag]
: 下载镜像下载镜像的时候是分层下载的,比如运行
docker pull mysql
下载mysql
镜像,输出如下:Using default tag: latest # 不写tag,默认下载tag为latest latest: Pulling from library/mysql 54fec2fa59d0: Already exists # 分层下载,前几层已经存在,故而不需要下载 bcc6c6145912: Already exists 951c3d959c9d: Already exists 05de4d0e206e: Already exists 319f0394ef42: Already exists d9185034607b: Already exists 013a9c64dadc: Already exists 42f3f7d10903: Pull complete # 仅需要下载不存在的层即可 c4a3851d9207: Pull complete Digest:sha256:61a2a33f4b8b4bc93b7b6b9e65e64044aaec594809f818aeffbff69a893d1944 # 摘要信息 Status: Downloaded newer image for mysql:latest docker.io/library/mysql:latest # 镜像真实位置
也可以在镜像名后加
tag
,指定版本,如:docker pull mysql:5.7
-
docker rmi 镜像id
: 删除镜像命令 意义 docker rmi -f 镜像id
删除单个镜像 docker rmi -f 镜像名:tag 镜像名:tag
删除多个镜像 docker rmi -f $(docker images -qa)
结合 docker images
命令,删除全部镜像
容器命令
-
docker run
: 新建容器并启动格式:
docker run [OPTIONS] IMAGE [COMMAND][ARG...]
常用参数:
参数 意义 例子 --name
给容器起名 --name="container01"
-d
以后台方式运行容器,并返回容器的id -i
以交互模式运行容器,通常和 -t
一起使用-t
重新指定一个终端,通常和 -i
一起使用docker run -it centos /bin/bash
-P
随机端口映射 -P 3306
将容器的3306
端口随机映射到宿主机的某个端口-p
指定端口映射 -p 3310:3306
将容器的3306
端口映射到宿主机的3310
端口 -
docker ps
: 列出所有(正在运行的)容器常用参数:
参数 意义 -a
列出所有容器(包括历史运行过的容器) -l
显示最近创建的容器 -n=?
显示最近n个创建的容器 -q
静默模式,只显示容器id -
退出容器
命令 意义 exit
停止容器并退出交互模式 Ctrl+P+Q 不停止容器并退出交互模式 -
启动停止容器
命令 意义 docker start (容器id or 容器名)
启动容器 docker restart (容器id or 容器名)
重启容器 docker stop (容器id or 容器名)
停止容器 docker kill (容器id or 容器名)
强制停止容器 -
docker rm 容器id
: 删除容器命令 意义 docker rm 容器id
删除单个容器 docker rm 容器id 容器id
删除多个容器 docker rm -f $(docker ps -qa)
结合 docker ps
命令,删除全部容器
其他常用命令
-
后台启动容器:
docker run -d 容器名
问题: 直接使用
docker run -d centos
以后台方式启动,结果发现该容器马上停止了,这是为什么呢?答案: Docker容器要想后台运行,就必须有一个前台进程.容器运行的命令如果不是那些一直挂起的命令,就会在命令执行完毕后自动退出.
-
查看日志:
docker logs 容器id
常用参数:
参数 意义 例子 -t
显示时间戳 -f
显示最近创建的容器 --tail 打印条数
显示多少条 -tail 10
例子:
docker logs -tf --tail 10 c8530dbbe3b4
-
查看容器中运行的进程信息:
docker top 容器id
使用
docker top 8dc189995333
命令查看某容器中运行的进程信息,输出结果如下:UID PID PPID C STIME TTY TIME CMD polkitd 22370 22350 0 00:36 ? 00:00:03 mysqld
-
查看容器/镜像的元数据:
docker inspect 容器id
使用
docker inspect 8dc189995333
命令查看某容器的元数据如下:[ { "Id": "8dc18999533348d6aeaa31b24f3d592f8c2a1738636d3a2eb53bdaf3efbbc4c0", "Created": "2021-02-07T10:27:37.756580818Z", "Path": "docker-entrypoint.sh", "Args": [...], "State": {"Status": "running"...}, "Image": "sha256:c8562eaf9d81c779cbfc318d6e01b8e6f86907f1d41233268a2ed83b2f34e748", "ResolvConfPath": "/var/lib/docker/containers/8dc18999533348d6aeaa31b24f3d592f8c2a1738636d3a2eb53bdaf3efbbc4c0/resolv.conf", "HostnamePath": "/var/lib/docker/containers/8dc18999533348d6aeaa31b24f3d592f8c2a1738636d3a2eb53bdaf3efbbc4c0/hostname", "HostsPath": "/var/lib/docker/containers/8dc18999533348d6aeaa31b24f3d592f8c2a1738636d3a2eb53bdaf3efbbc4c0/hosts", "LogPath": "/var/lib/docker/containers/8dc18999533348d6aeaa31b24f3d592f8c2a1738636d3a2eb53bdaf3efbbc4c0/8dc18999533348d6aeaa31b24f3d592f8c2a1738636d3a2eb53bdaf3efbbc4c0-json.log", "Name": "/musing_jepsen", "RestartCount": 0, "Driver": "overlay2", "Platform": "linux", "MountLabel": "", "ProcessLabel": "", "AppArmorProfile": "", "ExecIDs": null, "HostConfig": {"Binds": null...}, "GraphDriver": {"Name": "overlay2"...}, "Mounts": [...], "Config": {"Hostname": "8dc189995333"...}, "NetworkSettings": {"Bridge": ""...} ]
-
进入正在运行的容器
命令 意义 docker exec -it 容器id bashShell
进入容器并打开一个新终端,启动新线程 docker attach 容器id
进入容器并打开启动命令的终端,不会启动新进程 -
从容器内拷贝文件到主机上:
docker cp 容器id:容器内路径 目的主机路径
Docker命令小结
命令 | 意义 | 意义 |
---|---|---|
attach | Attach to a running container | 当前shell下attach连接指定运行镜像 |
build | Build an image from a Dockerfile | 通过Dockerfile定制镜像 |
commit | Create a new image from a container changes | 提交当前容器为新的镜像 |
cp | Copy files/folders from the containers filesystem to the host path | 从容器中拷贝指定文件或者目录到宿主机中 |
create | Create a new container | 创建一个新的容器,同run ,但不启动容器 |
diff | Inspect changes on a container’s filesystem | 查看Docker容器变化 |
events | Get real time events from the server | 从Docker服务获取容器实时事件 |
exec | Run a command in an existing container | 在已存在的容器上运行命令 |
export | Stream the contents of a container as a tar archive | 导出容器的内容流作为一个tar归档文件(对应import |
history | Show the history of an image | 展示一个镜像形成历史 |
images | List images | 列出系统当前镜像 |
import | Create a new filesystem image from the contents of a tarball | 从 tar包中的内容创建一个新的文件系统映像(对应export ) |
info | Display system-wide information | 显示系统相关信息 |
inspect | Return low-level information on a container | 查看容器详细信息 |
kill | Kill a running container | kill指定Docker容器 |
load | Load an image from a tar archive | 从一个tar包中加载一 个镜像(对应save ) |
login | Register or Login to the docker registry server | 注册或者登陆一个Docker源服务器 |
logout | Log out from a Docker registry server | 从当前Docker registry退出 |
logs | Fetch the logs of a container | 输出当前容器日志信息 |
port | Lookup the public-facing port which is NAT-ed to PRIVATE_PORT | 查看映射端口对应的容器内部源端口 |
pause | Pause all processes within a container | 暂停容器 |
ps | List containers | 列出容器列表 |
pull | Pull an image or a repository from the docker registry server | 从Docker镜像源服务器拉取指定镜像或者库镜像 |
push | Push an image or a repository to the docker registry server | 推送指定镜像或者库镜像至Docker源服务器 |
restart | Restart a running container | 重启运行的容器 |
rm | Remove one or more containers | 移除一个或者多个容器 |
rmi | Remove one or more images | 移除一个或多个镜像(无容器使用该镜像才可删除,否则需删除相关容器才可继续或-f 强制删除 |
run | Run a command in a new container | 创建一个新的容器并运行一个命令 |
save | Save an image to a tar archive | 保存一个镜像为一个tar包(对应load ) |
search | Search for an image on the Docker Hub | 在Docker Hub中搜索镜像 |
start | Start a stopped containers | 启动容器 |
stop | Stop a running containers | 停止容器 |
tag | Tag an image into a repository | 给源中镜像打标签 |
top | Lookup the running processes of a container | 查看容器中运行的进程信息 |
unpause | Unpause a paused container | 取消暂停容器 |
version | Show the docker version information | 查看Docker版本号 |
wait | Block until a container stops, then print its exit code | 截取容器停止时的退出状态值 |
练习1: 使用Docker安装Nginx
-
搜索镜像:
docker search nginx
,输出结果如下:NAME DESCRIPTION STARS OFFICIAL AUTOMATED nginx Official build of Nginx. 14445 [OK] jwilder/nginx-proxy Automated Nginx reverse proxy for docker con… 1965 [OK] richarvey/nginx-php-fpm Container running Nginx + PHP-FPM capable of… 807 [OK] jc21/nginx-proxy-manager Docker container for managing Nginx proxy ho… 146 linuxserver/nginx An Nginx container, brought to you by LinuxS… 141 tiangolo/nginx-rtmp Docker image with Nginx using the nginx-rtmp… 113 [OK] ...
-
拉取镜像:
docker pull nginx
,输出如下:Using default tag: latest latest: Pulling from library/nginx a076a628af6f: Already exists 0732ab25fa22: Pull complete d7f36f6fe38f: Pull complete f72584a26f32: Pull complete 7125e4df9063: Pull complete Digest: sha256:10b8cc432d56da8b61b070f4c7d2543a9ed17c2b23010b43af434fd40e2ca4aa Status: Downloaded newer image for nginx:latest docker.io/library/nginx:latest
-
启动容器:
docker run -d --name mynginx -p 3500:80 nginx
,输出如下:58d4ab1e9f71cb183cc68e98d5cd2ceb6e856b65f85dd70fb378afb254f6c0d4 # 这是该容器的container ID
-
测试访问:
curl localhost:3500
,输出如下:<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
得到上面结果说明Nginx启动成功.
-
查看容器状态:
docker ps
,输出如下:CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 58d4ab1e9f71 nginx "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:3500->80/tcp mynginx
-
进入容器,查看
index.html
-
进入容器:
docker exec -it mynginx /bin/bash
-
查询nginx的路径:
whereis nginx
,输出如下:/usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx
-
查看
index.html
:cat /usr/share/nginx/html/index.html
,输出如下:<!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html>
-
练习2: 使用Docker安装Tomcat
- 下载Tomcat镜像:
docker pull tomcat
- 启动:
docker run -d -p 8080:8080 --name tomcat01 tomcat
- 进入Tomcat:
docker exec -it tomcat9 /bin/bash
- 部署项目
练习3: 使用Docker部署Elasticsearch
使用Docker部署Elasticsearch需要考虑如下几个问题:
- 端口暴露问题
9200
,9300
. - 数据卷的挂载问题
data
,plugins
,conf
- 内存问题:
"ES_JAVA_OPTS=-Xms512m -Xmx512m"
实验步骤如下:
-
先启动一个Elasticsearch镜像:
docker run -d --name elasticsearch01 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.6.2
-
启动成功之后发现服务器很卡,使用
docker stats
命令查看容器状态:docker stats elasticsearch01
,输出如下:CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % 249ae46da625 elasticsearch01 0.00% 1.036GiB / 1.716GiB 60.37%
发现是Elasticsearch占用内存太大了.
-
关闭这个Elasticsearch镜像:
docker stop elasticsearch01
-
启动Elasticsearch镜像是增加内存限制:
docker run -d --name elasticsearch02 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m" elasticsearch:7.6.2
-
使用
docker stats
命令查看容器状态:docker stats elasticsearch02
,输出如下:CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % bea4a52fb840 elasticsearch02 0.45% 404.4MiB / 1.795GiB 22.00%
发现这次服务就不卡了
-
测试访问:
curl localhost:9200
,输出如下:{ "name" : "bea4a52fb840", "cluster_name" : "docker-cluster", "cluster_uuid" : "-adcnQWoS3S0KR4YBsDWBA", "version" : { "number" : "7.6.2", "build_flavor" : "default", "build_type" : "docker", "build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f", "build_date" : "2020-03-26T06:34:37.794943Z", "build_snapshot" : false, "lucene_version" : "8.4.0", "minimum_wire_compatibility_version" : "6.8.0", "minimum_index_compatibility_version" : "6.0.0-beta1" }, "tagline" : "You Know, for Search" }
容器数据卷
随着Docker容器的删除,其中的数据也被删除了,对于数据库等应用来说这样不合适.
可以使用容器数据卷将数据挂在到本地,卷就是目录或者文件,存在于一个或者多个容器中,由Docker挂载到容器,但不属于联合文件系统,因此能够绕过 Union File System,提供一些用于持续存储或共享数据的特性.
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷.
容器数据卷的特点:
- 数据卷可在容器之间共享或重用数据
- 卷中的更改可以直接生效
- 数据卷中的更改不会包含在镜像的更新中
- 数据卷的生命周期一直持续到没有容器使用它为止
卷就是容器的持久化,以及容器间的继承和数据共享.
容器数据卷的挂载
使用docker run
命令的-v
参数添加数据卷,命令格式如下:
docker run -v 挂载路径映射 镜像名
根据挂载方式不同,挂载路径映射
的写法不同.
指定路径挂载
对于指定路径挂载,挂载路径映射
的格式是宿主机路径:容器内路径
,例如:
docker run --name centos01 -it -v /home/ceshi:/home centos /bin/bash
上述命令将容器内的/home
路径挂载到宿主机的/home/ceshi
路径上.
使用docker inspect
命令可以查看容器数据卷: docker inspect centos01
,得到输出如下,可以在Mounts
节点下看到挂载的容器数据卷.
[
{
"Id": "a36ee54bd1f03b30820490171ef6f058dea39b5c4d1350013be462b6baa6ff09",
// ...
"Mounts": [
{
"Type": "bind",
"Source": "/home/ceshi",
"Destination": "/home",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
// ...
}
]
不论容器是否运行,在容器外对/home/ceshi
的操作和在容器内对/home
的操作是一致的.
下面演示使用容器数据卷将MySQL数据库中的数据持久化:
-
启动MySQL数据库时除了使用
-v
参数指定数据卷挂载,-p
参数指定端口映射之外,还要使用-e
参数指定MySQL的root
用户的密码,见Docker Hub上MySQL镜像的页面.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
上述命令将容器内的MySQL配置文件
/etc/mysql/conf.d
映射到宿主机的/home/mysql/conf
文件上,将容器内MySQL数据文件目录/var/lib/mysql
映射到宿主机/home/mysql/data
目录上. -
使用
docker inspect
命令查看数据卷挂载信息:docker inspect mysql01
,得到结果如下,可以看到端口映射和数据卷挂载信息:[ { "Id": "b9b5aa7a5a18790c3b69ee7531c825da8585e3b7c71741cff749ad9841d9db0e", "Name": "/mysql01", // ... "Mounts": [ // 数据卷挂载信息 { // MySQL配置文件"etc/mysql/conf.d"映射到宿主机的"/home/mysql/conf"文件上 "Type": "bind", "Source": "/home/mysql/conf", "Destination": "/etc/mysql/conf.d", "Mode": "", "RW": true, "Propagation": "rprivate" }, { // MySQL数据文件目录"/var/lib/mysql"映射到宿主机"/home/mysql/data"目录上 "Type": "bind", "Source": "/home/mysql/data", "Destination": "/var/lib/mysql", "Mode": "", "RW": true, "Propagation": "rprivate" } ], // ... "NetworkSettings": { // ... "Ports": { // 容器的3306端口映射到宿主机的3310端口上 "3306/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "3310" } ], "33060/tcp": null }, // ... } } ]
-
使用数据库客户端工具连接宿主机的
3310
端口就能对容器中的MySQL数据库进行操作,在数据库中创建数据后查看宿主机的/home/mysql
目录,发现存在对应的文件. -
使用
docker rm -f mysql01
命令删除容器,发现主机的/home/mysql
目录下的文件依然存在.
匿名挂载和具名挂载
出于可移植性的考虑,指定目录挂载的数据卷挂载方式不常用,因为宿主机路径是依赖于特定宿主机的,并不能够保证所有宿主机上都存在对应目录.
常见的数据卷挂载方式有两种:
- 匿名挂载: 指定数据卷在容器内的路径,
挂载路径映射
的格式是容器内路径
. - 具名挂载: 指定数据卷在容器内的路径并为数据卷命名,
挂载路径映射
的格式是卷名:容器内路径
.
例如:
docker run -d -P --name nginx01 -v /etc/nginx nginx # 匿名挂载
docker run -d -P --name nginx02 -v nginx-volume:/etc/nginx nginx # 具名挂载
为了区分具名挂载和指定路径挂载,指定路径挂载中的宿主机路径必须是绝对路径(以
/
开头);而具名挂载中的卷名不得以/
开头.
使用docker inspect
命令可以查看数据卷挂载信息:
-
运行命令
docker inspect nginx01
,输出如下:[ { "Id": "f483cc589ff041580e114d04b5a03324e62a24c44b68485d9ed4cf2fa75f4c31", "Name": "/nginx01", // ... "Mounts": [ { // 匿名挂载,"Name"属性为随机hash "Type": "volume", "Name": "689f2a4c1b6b95079378d64fe468f256e2639ebd6aaaae2028e3cd7f33f146a0", "Source": "/var/lib/docker/volumes/689f2a4c1b6b95079378d64fe468f256e2639ebd6aaaae2028e3cd7f33f146a0/_data", "Destination": "/etc/nginx", "Driver": "local", "Mode": "", "RW": true, "Propagation": "" } ] // ... } ]
-
运行命令
docker inspect nginx02
,输出如下:[ { "Id": "f483cc589ff041580e114d04b5a03324e62a24c44b68485d9ed4cf2fa75f4c31", "Name": "/nginx02", // ... "Mounts": [ { // 具名挂载,"Name"属性为指定的卷名 "Type": "volume", "Name": "nginx-volume", "Source": "/var/lib/docker/volumes/nginx-volume/_data", "Destination": "/etc/nginx", "Driver": "local", "Mode": "z", "RW": true, "Propagation": "" } ], // ... } ]
使用具名挂载和匿名挂载的数据卷在宿主机的/var/lib/docker/volumes
目录下.使用docker volume ls
命令可以查看所有容器数据卷:
DRIVER VOLUME NAME
local f38b9f3afda1f8511ce46668a7edd40b581c001426ca5fc2b96a11ae9038b654
local nginx-volume
指定读写权限
在挂载路径映射
后加上:读写权限
可以设置容器对该数据卷的读写权限,读写权限有两种:
ro
: 只读权限rw
: 读写权限
示例如下:
docker run -d -P --name nginx02 -v nginxconfig:/etc/nginx:ro nginx # 只读
docker run -d -P --name nginx02 -v nginxconfig:/etc/nginx:rw nginx # 读写
容器间数据共享
可以通过数据卷实现容器间数据的共享,在创建容器时使用--volumes-from 父容器
参数指定从父容器中继承(共享)数据卷,实现父子容器间的数据卷共享.示例如下:
-
创建父容器
container01
,指定两个匿名挂载卷/data_volume1
和/data_volume1
:docker run --name container01 -v /data_volume1 -v /data_volume2 -it centos /bin/bash
-
打开另外两个ssh窗口,分别创建子容器
container02
,container03
,使用--volumes-from container01
参数指定从container01
中继承数据卷.docker run --name container02 --volumes-from container01 -it centos /bin/bash docker run --name container03 --volumes-from container01 -it centos /bin/bash
-
在某个容器中修改贡献路径下的数据,另外两个容器中的数据同步发生变化.
-
删除某个容器,另外两个容器中的数据不受影响.
-
删除所有容器,宿主机内的数据仍存在.
得出结论: 容器数据卷的生命周期一直持续到没有容器使用它为止,存储在本机的文件则会一直保留.
Docker镜像
Docker镜像原理
Linux操作系统由内核空间和用户空间组成,内核空间是kernel,Linux刚启动时会加载bootfs文件系统,之后bootfs会被卸载掉.
Dcoker镜像通过共享bootfs层模拟出多种操作系统环境
镜像是分层的:
在下载镜像时,可以看到镜像是分层下载的: docker pull redis
,输出如下:
Using default tag: latest
latest: Pulling from library/redis
a076a628af6f: Already exists
f40dd07fe7be: Pull complete
ce21c8a3dbee: Pull complete
ee99c35818f8: Pull complete
56b9a72e68ff: Pull complete
3f703e7f380f: Pull complete
Digest: sha256:0f97c1c9daf5b69b93390ccbe8d3e2971617ec4801fd0882c72bf7cad3a13494
Status: Downloaded newer image for redis:latest
docker.io/library/redis:latest
使用docker inspect
命令也可以查看Docker镜像的层次结构: docker inspect redis
,可以在RootFS/Layers
节点下看到该镜像的层次结构:
[
{
"Id": "sha256:621ceef7494adfcbe0e523593639f6625795cc0dc91a750629367a8c7b3ccebb",
// ...
"RootFS": {
"Type": "layers",
"Layers": [ // 该镜像的6层结构
"sha256:cb42413394c4059335228c137fe884ff3ab8946a014014309676c25e3ac86864",
"sha256:8e14cb7841faede6e42ab797f915c329c22f3b39026f8338c4c75de26e5d4e82",
"sha256:1450b8f0019c829e638ab5c1f3c2674d117517669e41dd2d0409a668e0807e96",
"sha256:f927192cc30cb53065dc266f78ff12dc06651d6eb84088e82be2d98ac47d42a0",
"sha256:a24a292d018421783c491bc72f6601908cb844b17427bac92f0a22f5fd809665",
"sha256:3480f9cdd491225670e9899786128ffe47054b0a5d54c48f6b10623d2f340632"
]
},
// ...
}
]
Docker镜像的创建
创建新镜像有两种方式: 通过docker commit
命令commit容器 和 通过docker build
命令根据Dockerfile创建镜像.
镜像commit
容器本质上是在镜像最上层上方附加的可写的一层.
可以使用docker commit
命令将该层的修改固化下来,生成新的镜像,语法如下:
docker commit -m="提交的描述信息" -a="作者" 容器id 要创建的目标镜像名:[标签名]
下面以Tomcat镜像为例,演示如何基于已有镜像生成新镜像:
-
默认下载的Tomcat镜像运行之后访问其8080端口会得到
404
错误,因为其/usr/local/tomcat/webapps
目录为空.-
先使用
docker run -d -p 8080:8080 --name tomcat01 tomcat
命令启动Tomcat容器 -
使用
curl localhost:8080
访问8080
端口,得到结果如下:<!doctype html><html lang="en"><head><title>HTTP Status 404 – Not Found</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 404 – Not Found</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Description</b> The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.</p><hr class="line" /><h3>Apache Tomcat/9.0.41</h3></body></html>
可以看到
404
错误 -
进入docker容器,查看其
/usr/local/tomcat/webapps
目录,发现其内容为空:docker exec -it tomcat01 /bin/bash ls -l /usr/local/tomcat/webapps
输出如下:
total 0
-
可以看到
webapps
中的默认内容都在webapps.dist
目录下:ls -l /usr/local/tomcat/webapps.dist
total 20 drwxr-xr-x 3 root root 4096 Jan 13 08:25 ROOT drwxr-xr-x 15 root root 4096 Jan 13 08:25 docs drwxr-xr-x 7 root root 4096 Jan 13 08:25 examples drwxr-xr-x 6 root root 4096 Jan 13 08:25 host-manager drwxr-xr-x 6 root root 4096 Jan 13 08:25 manager
-
-
因此我们启动一个新Tomcat容器,将
webapps.dist
目录下的内容复制到webapps
目录下:-
启动Tomcat容器并进入工作目录:
docker run -d -p 8080:8080 --name tomcat02 tomcat
-
进入容器:
docker exec -it tomcat02 /bin/bash
-
将
webapps.dist
目录下的内容复制到webapps
目录下:cp -r webapps.dist/* webapps
-
退出容器: Ctrl+P+Q
-
访问
8080
端口:curl localhost:8080
,得到输出如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Apache Tomcat/9.0.41</title> <link href="favicon.ico" rel="icon" type="image/x-icon" /> <link href="tomcat.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="wrapper"> <div id="navigation" class="curved container"> <span id="nav-home"><a href="https://tomcat.apache.org/">Home</a></span> <span id="nav-hosts"><a href="/docs/">Documentation</a></span> <span id="nav-config"><a href="/docs/config/">Configuration</a></span> <span id="nav-examples"><a href="/examples/">Examples</a></span> <span id="nav-wiki"><a href="https://wiki.apache.org/tomcat/FrontPage">Wiki</a></span> <span id="nav-lists"><a href="https://tomcat.apache.org/lists.html">Mailing Lists</a></span> <span id="nav-help"><a href="https://tomcat.apache.org/findhelp.html">Find Help</a></span> <br class="separator" /> </div> </div> <!-- ... --> </body> </html>
-
-
通过上述步骤,我们已构建了一个可访问的Tomcat容器,之后可以通过
docker commit
命令将上述容器打包成镜像docker commit -a="chenhai" -m="copy webapps.dist/* to webapps" tomcat02 my_tomcat_image
-
使用
docker images
命令,可以看到我们打包的my_tomcat_image
镜像:REPOSITORY TAG IMAGE ID CREATED SIZE my_tomcat_image latest 919273b2c954 14 seconds ago 654MB tomcat latest 040bdb29ab37 5 weeks ago 649MB
-
使用
docker inspect
命令分别查看tomcat
镜像和my_tomcat_image
镜像的"Layers"
属性,可以看出my_tomcat_image
是基于tomcat
基础上构建的-
运行
docker inspect tomcat
命令,输出如下:[ { // ... "RootFS": { "Type": "layers", "Layers": [ // tomcat镜像有10层 "sha256:4762552ad7d851a9901571428078281985074e5ddb806979dd7ad24748db4ca0", "sha256:a1f2f42922b1d3aa948f2569adebb37129941e889f13b96823a5e2aa8ecc1a8f", "sha256:ef9a7b8862f4797ec08f3733ab9fc2a08c51f44e5561b087eb10a6e442599760", "sha256:aa7af8a465c6b600b7151db82799f757d0029c7fb9f170faaffdc40080c525c5", "sha256:7496c5e8691b7de622ad5c1e0f9b300df55e923b960fc75e7b7cf1a297a36da8", "sha256:7a9b35031285d77ce1d4e73b9eabb0a8f204b0729c3f4c342bca4e57b73fbae1", "sha256:500f722b156b012903f8a0af13cfb2a8521be6b2126071f4330714b6fa5bd9d7", "sha256:8e2e6f2527c7c2e81725a0d539a04281d8a32b26bf2ca57fe5f5ffe20862625d", "sha256:c9132b9a3fc8c30239edb774bdf01b252e1cbe945c3994e4fa12929cc93831d4", "sha256:9ddc8cd8299b64510aee4f540827bf2c5cf326aa73fdd47a599a1ee0d9a8c224" ] }, // ... } ]
-
运行
docker inspect my_tomcat_image
命令,输出如下:[ { // ... "RootFS": { "Type": "layers", "Layers": [ // my_tomcat_image镜像有11层,可以看到前10层与tomcat镜像相同 "sha256:4762552ad7d851a9901571428078281985074e5ddb806979dd7ad24748db4ca0", "sha256:a1f2f42922b1d3aa948f2569adebb37129941e889f13b96823a5e2aa8ecc1a8f", "sha256:ef9a7b8862f4797ec08f3733ab9fc2a08c51f44e5561b087eb10a6e442599760", "sha256:aa7af8a465c6b600b7151db82799f757d0029c7fb9f170faaffdc40080c525c5", "sha256:7496c5e8691b7de622ad5c1e0f9b300df55e923b960fc75e7b7cf1a297a36da8", "sha256:7a9b35031285d77ce1d4e73b9eabb0a8f204b0729c3f4c342bca4e57b73fbae1", "sha256:500f722b156b012903f8a0af13cfb2a8521be6b2126071f4330714b6fa5bd9d7", "sha256:8e2e6f2527c7c2e81725a0d539a04281d8a32b26bf2ca57fe5f5ffe20862625d", "sha256:c9132b9a3fc8c30239edb774bdf01b252e1cbe945c3994e4fa12929cc93831d4", "sha256:9ddc8cd8299b64510aee4f540827bf2c5cf326aa73fdd47a599a1ee0d9a8c224", "sha256:4bf57ab98abebba174523e47d442ba9dd58213ac1e28b12df21f0f10bb708146" ] }, // ... } ]
-
Dockerfile
从Dockerfile构建Docker镜像的步骤:
- 编写Dockerfile文件
docker build
构建镜像docker run
Dockerfile的常见指令如下:
指令 | 意义 |
---|---|
FROM | 基础镜像,指定当前新镜像是基于哪个镜像的 |
MAINTAINER | 镜像维护者的姓名混合邮箱地址 |
RUN | 容器构建时需要运行的命令 |
EXPOSE | 当前容器对外保留出的端口 |
WORKDIR | 指定在创建容器后,终端默认登录的进来工作目录,一个落脚点 |
ENV | 用来在构建镜像过程中设置环境变量 |
ADD | 将宿主机目录下的文件拷贝进镜像且ADD 命令会自动处理URL和解压tar压缩包 |
COPY | 类似ADD ,拷贝文件和目录到镜像中! |
VOLUME | 容器数据卷,用于数据保存和持久化工作 |
CMD | 指定一个容器启动时要运行的命令,dockerFile中可以有多个CMD 指令,但只有最后一个生效 |
ENTRYPOINT | 指定一个容器启动时要运行的命令,和CMD 一样 |
ONBUILD | 当构建一个被继承的Dockerfile时运行命令,父镜像在被子镜像继承后,父镜像的ONBUILD 被触发 |
下面以CentOS官方镜像为例,展示一个Dockerfile的内容,见CentOS的Dockerfile:
FROM scratch # base镜像,最基础的镜像
ADD centos-8-x86_64.tar.xz / # 将rootfs添加到根目录并解压
LABEL org.label-schema.schema-version="1.0" org.label-schema.name="CentOS Base Image" org.label-schema.vendor="CentOS" org.label-schema.license="GPLv2" org.label-schema.build-date="20201204"
CMD ["/bin/bash"] # 启动命令行
练习1: 构建自定义CentOS镜像
CentOS的官方Docker镜像比较精简,没有很多常用的工具,登陆后的默认路径的是根目录/
.下面我们在此基础上进行修改,增加vim
和net-tools
,并将登陆后的默认路径修改为root
用户的家目录.步骤如下:
-
编写
Dockerfile
文件:FROM centos # 基于centos镜像构建 MAINTAINER chenh<123@qq.com> # 作者姓名和邮箱 ENV MYPATH /root # 设置环境变量 WORKDIR $MYPATH # 设置默认登陆目录 RUN yum -y install vim # 安装vim RUN yum -y install net-tools # 安装net-tools EXPOSE 80 # 暴露80端口 CMD echo $MYPATH # 输出调试信息 CMD echo "---end---" # 输出调试信息 CMD /bin/bash # 进入bash
-
进入
Dockerfile
所在目录,执行docker build
命令构建镜像:docker build
语法如下:docker build -f dockerfile地址 -t 新镜像名字:TAG .
注意最后有一个
.
,表示当前目录.本实验构建
my-centos
镜像的命令如下:docker build -f Dockerfile -t my-centos .
输出如下
Step 1/10 : FROM centos ---> 300e315adb2f Step 2/10 : MAINTAINER chenh<123@qq.com> ---> Running in c44ab3a18589 Removing intermediate container c44ab3a18589 ---> 7cd26ffc364d Step 3/10 : ENV MYPATH /root ---> Running in 1b0e6f4f4fa2 Removing intermediate container 1b0e6f4f4fa2 ---> 741fb6aecb01 Step 4/10 : WORKDIR $MYPATH ---> Running in a2b56ea26c54 Removing intermediate container a2b56ea26c54 ---> 71e9bb4c397f Step 5/10 : RUN yum -y install vim ---> Running in a31932ed0c54 CentOS Linux 8 - AppStream 1.6 MB/s | 6.3 MB 00:03 CentOS Linux 8 - BaseOS 588 kB/s | 2.3 MB 00:03 CentOS Linux 8 - Extras 8.8 kB/s | 8.6 kB 00:00 Dependencies resolved. Install 5 Packages Complete! Removing intermediate container a31932ed0c54 ---> 5d1a36c2ee7a Step 6/10 : RUN yum -y install net-tools ---> Running in 60c7b23e18ec Last metadata expiration check: 0:00:14 ago on Thu Feb 18 11:52:47 2021. Dependencies resolved. Total download size: 322 k Installed size: 942 k Downloading Packages: Complete! Removing intermediate container 60c7b23e18ec ---> 7c09e53a2051 Step 7/10 : EXPOSE 80 ---> Running in 2f7481e6d0e0 Removing intermediate container 2f7481e6d0e0 ---> 3b2bb84afa87 Step 8/10 : CMD echo $MYPATH ---> Running in 08a3a22956c2 Removing intermediate container 08a3a22956c2 ---> 6aef0d4c26df Step 9/10 : CMD echo "---end---" ---> Running in 9a21ec5aa63e Removing intermediate container 9a21ec5aa63e ---> 494ed5a122ad Step 10/10 : CMD /bin/bash ---> Running in 3ccac448e32d Removing intermediate container 3ccac448e32d ---> 7e2c3b0cc70e Successfully built 7e2c3b0cc70e Successfully tagged my-centos:latest
可以看到Docker逐句解析并执行
Dockerfile
,每执行一次就生成一个新层. -
运行容器:
docker run -it my-centos
运行该容器后,发现默认进入到
/root
目录下,且vim
和ifconfig
命令均能正常工作. -
列出镜像变更历史:
docker history my-centos
可以看到镜像的变更历史如下:每一条变更历史对应一层
IMAGE CREATED CREATED BY SIZE COMMENT 7e2c3b0cc70e 6 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "/bin… 0B 494ed5a122ad 6 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B 6aef0d4c26df 6 minutes ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "echo… 0B 3b2bb84afa87 6 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B 7c09e53a2051 6 minutes ago /bin/sh -c yum -y install net-tools 23.3MB 5d1a36c2ee7a 6 minutes ago /bin/sh -c yum -y install vim 58.1MB 71e9bb4c397f 7 minutes ago /bin/sh -c #(nop) WORKDIR /root 0B 741fb6aecb01 7 minutes ago /bin/sh -c #(nop) ENV MYPATH=/root 0B 7cd26ffc364d 7 minutes ago /bin/sh -c #(nop) MAINTAINER chenh<123@qq.c… 0B 300e315adb2f 2 months ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B <missing> 2 months ago /bin/sh -c #(nop) LABEL org.label-schema.sc… 0B <missing> 2 months ago /bin/sh -c #(nop) ADD file:bd7a2aed6ede423b7… 209MB
练习2:构建自定义Tomcat镜像
-
编写
Dockerfile
如下:FROM centos MAINTAINER chenh<123@qq.com> #把java与tomcat添加到容器中 ADD jdk-8u11-linux-x64.tar.gz /usr/local/ ADD apache-tomcat-9.0.22.tar.gz /usr/local/ #安装vim编辑器 RUN yum -y install vim #设置工作访问时候的WORKDIR路径,登录落脚点 ENV MYPATH /usr/local WORKDIR $MYPATH #配置java与tomcat环境变量 ENV JAVA_HOME /usr/local/jdk1.8.0_11 ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.22 ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.22 ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin #容器运行时监听的端口 EXPOSE 8080 #启动时运行tomcat CMD /usr/local/apache-tomcat-9.0.22/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.22/bin/logs/catalina.out
-
构建镜像:
docker build -t my-tomcat .
-
运行容器:
docker run -d -p 9090:8080 --name my-tomcat \ -v /home/tomcat/test:/usr/local/apache-tomcat9.0.22/webapps/test \ -v /home/tomcat/tomcat9logs:/usr/local/apache-tomcat9.0.22/logs \ --privileged=true \ my-tomcat
使用
--privileged=true
是为了避免因为权限报错 -
运行
curl localhost:9090
命令,得到结果如下:<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Apache Tomcat/9.0.41</title> <link href="favicon.ico" rel="icon" type="image/x-icon" /> <link href="tomcat.css" rel="stylesheet" type="text/css" /> </head> <body> <div id="wrapper"> <div id="navigation" class="curved container"> <span id="nav-home"><a href="https://tomcat.apache.org/">Home</a></span> <span id="nav-hosts"><a href="/docs/">Documentation</a></span> <span id="nav-config"><a href="/docs/config/">Configuration</a></span> <span id="nav-examples"><a href="/examples/">Examples</a></span> <span id="nav-wiki"><a href="https://wiki.apache.org/tomcat/FrontPage">Wiki</a></span> <span id="nav-lists"><a href="https://tomcat.apache.org/lists.html">Mailing Lists</a></span> <span id="nav-help"><a href="https://tomcat.apache.org/findhelp.html">Find Help</a></span> <br class="separator" /> </div> </div> <!-- ... --> </body> </html>
-
进入
/home/tomcat/test
目录下,发布一个程序:创建
web.xml
,内容如下:<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>test</display-name> </web-app>
创建
a.jsp
,内容如下:<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>hello,kuangshen</title> </head> <body> -----------welcome------------ <%=" my docker tomcat,kuangshen666 "%> <br> <br> <% System.out.println("-------my docker tomcat-------");%> </body> </html>
-
运行
curl localhost:9090/test/a.jsp
,得到结果如下:<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>hello,kuangshen</title> </head> <body> -----------welcome------------ my docker tomcat,kuangshen666 <br> <br> -------my docker tomcat------- </body> </html>
发布镜像
本节略
Docker网络
在研究Docker网络之前,先删除所有镜像和容器:
docker rm -f $(docker ps -a -q) # 删除所有容器
docker rmi -f $(docker images -qa) # 删除全部镜像
桥接网卡docker0
在宿主机中使用ip addr
查看网卡信息如下:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:2f:80:19 brd ff:ff:ff:ff:ff:ff
inet 172.21.16.4/20 brd 172.21.31.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe2f:8019/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:aa:6b:53:81 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:aaff:fe6b:5381/64 scope link
valid_lft forever preferred_lft forever
可以看到宿主机有3块网卡,分别是回环网卡lo
,物理网卡eth0
和Docker桥接网卡docker0
-
为了验证Docker容器之间是否能够通信,创建一个容器
tomcat01
并查询其网卡信息:docker run -d -P --name tomcat01 tomcat docker exec -it tomcat01 ip addr
可以看到
tomcat01
容器的网卡信息如下:1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 136: eth0@if137: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
在宿主机中尝试
ping
容器tomcat01
:ping 172.17.0.2
,发现能够ping通:64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.047 ms 64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.053 ms 64 bytes from 172.17.0.2: icmp_seq=3 ttl=64 time=0.055 ms 64 bytes from 172.17.0.2: icmp_seq=4 ttl=64 time=0.056 ms ...
再次在宿主机使用
ip addr
命令查看网卡信息,发现宿主机多出了一块网卡veth2387536@if136
:1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 # ... 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 # ... 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default # ... 137: veth2387536@if136: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 52:05:09:20:5c:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::5005:9ff:fe20:5cf9/64 scope link valid_lft forever preferred_lft forever
我们发现宿主机的
veth2387536@if136
和容器内的eth0@if137
组成了一对网卡. -
再启动一个容器
tomcat02
,发现宿主机内又多了一个和容器tomcat02
内网卡相对应的网卡.docker run -d -P --name tomcat02 tomcat docker exec -it tomcat02 ip addr
容器
tomcat02
内网卡信息如下:1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 138: eth0@if139: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever
宿主机内网卡信息如下:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 # ... 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 # ... 3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default # ... 137: veth2387536@if136: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 52:05:09:20:5c:f9 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::5005:9ff:fe20:5cf9/64 scope link valid_lft forever preferred_lft forever 139: veth2bed2bf@if138: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default link/ether 96:90:58:5a:80:62 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::9490:58ff:fe5a:8062/64 scope link valid_lft forever preferred_lft forever
-
下面测试宿主机与容器之间能否
ping
通:-
在宿主机内通过IP地址
ping
容器tomcat01
,tomcat02
,能够ping通.ping 172.17.0.2 # 在宿主机内ping容器tomcat01的IP地址,能ping通 ping 172.17.0.3 # 在宿主机内ping容器tomcat02的IP地址,能ping通
-
在容器
tomcat01
内通过IP地址ping
宿主机和容器tomcat02
,能够ping通.docker exec -it tomcat01 ping 172.17.0.1 # 在容器tomcat01内ping宿主机的IP地址,能ping通 docker exec -it tomcat01 ping 172.17.0.3 # 在容器tomcat01内ping容器tomcat02的IP地址,能ping通
-
在容器
tomcat02
内通过IP地址ping
宿主机和容器tomcat01
,能够ping通.docker exec -it tomcat02 ping 172.17.0.1 # 在容器tomcat02内ping宿主机的IP地址,能ping通 docker exec -it tomcat02 ping 172.17.0.1 # 在容器tomcat02内ping容器tomcat01的IP地址,能ping通
-
结论: Docker在宿主机内安装的虚拟网卡docker0
是一个桥接网卡,使用了veth-pair技术.
--link
使用docker0
作为桥接网卡可以实现容器间的通信,但是这种方法存在一个局限: 容器间只能通过IP地址进行通信,不能通过容器名或容器id进行通信.
ping 172.17.0.2 # 在宿主机内ping容器tomcat01的IP地址,能ping通
ping tomcat01 # 在宿主机内ping容器tomcat01的容器名,不能ping通
docker exec -it tomcat01 ping 172.17.0.3 # 在容器tomcat01内ping容器tomcat02的IP地址,能ping通
docker exec -it tomcat01 ping tomcat02 # 在容器tomcat01内ping容器tomcat02的容器名,不能ping通
通过在启动容器时添加--link 要连接的容器名或id
参数,可以实现通过容器名或id进行通信,示例如下:
docker run -d -P --name tomcat03 --link tomcat02 tomcat
创建容器tomcat03
连接到容器tomcat02
中,这样就可以在容器tomcat03
中t那个锅容器名或容器id来ping容器tomcat02
docker exec -it tomcat03 ping 172.17.0.3 # 在容器tomcat03内ping容器tomcat02的IP地址,能ping通
docker exec -it tomcat03 ping tomcat02 # 在容器tomcat03内ping容器tomcat02的容器名,能ping通
docker exec -it tomcat03 ping 94cd188b01ea # 在容器tomcat03内ping容器tomcat02的容器id,能ping通
--link
机制是通过修改/etc/hosts
文件实现的,进入容器tomcat03
查看其/etc/hosts
文件: docker exec -it tomcat03 cat /etc/hosts
如下:
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3 tomcat02 94cd188b01ea # 容器tomcat02的容器名和容器id
172.17.0.4 4ea43009c443 # 容器tomcat03自身的容器id
因为
--link
是通过修改hosts
文件实现连接的,因此不能实现双向连接,不推荐使用.
自定义网络
使用docker network
命令可以对Docker网络进行操作.
首先使用docker network ls
命令列出所有网络,输出如下:
NETWORK ID NAME DRIVER SCOPE
2c2577c5c04e bridge bridge local
8a11f7f5238a host host local
c77811056092 none null local
其中DRIVER
属性表示网络模式,具体如下:
网络模式 | docker run 命令中的参数 | 意义 |
---|---|---|
bridge | --net=bridge ,是docker run 命令的默认参数 | 在Docker网桥docker0 上为容器创建新的网络栈 |
none | --net=none | 不配置网络,用户可以稍后进入容器,自行配置 |
container | --net=container:name/id | 和另外一个容器共享Network namespace |
host | --net=host | 容器和宿主机共享Network namespace |
用户自定义 | --net=自定义网络名 | 用户自己使用network相关命令定义网络,创建容器的时候可以指定为自己定义的网络 |
使用docker run
命令创建容器时,默认参数是--net=bridge
,即通过docker0
网桥组网.
使用docker network inspect
可以查看网络的详细信息:
docker network inspect bridge
输出如下,可以在Containers
节点中看到该网络下有我们之前创建的tomcat01
,tomcat02
,tomcat03
3个容器:
[
{
"Name": "bridge",
"Id": "2c2577c5c04e67dcff83b9509b44d249f6b8f28391550a9835d840f2f1349a79",
"Created": "2021-02-17T23:23:27.045956359+08:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "172.17.0.0/16",
"Gateway": "172.17.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": { // 我们之前创建的3个容器
"3c35894e46aedb420bf560a9db4f393012a999ba99dc5679077ee3ac8113b7b4": {
"Name": "tomcat01",
"EndpointID": "739cc96286e2bb55526b68ff0a9eab3aa65a8c09a9e524abb7d8f3630c6f5aec",
"MacAddress": "02:42:ac:11:00:02",
"IPv4Address": "172.17.0.2/16",
"IPv6Address": ""
},
"4ea43009c443efb3ba5fb0c247d63904c252092e1055e67bb70f140d35a38ee9": {
"Name": "tomcat03",
"EndpointID": "6eaad11d71a7852e99d0ad04c2feda731e69744f273f541c98e176fc512bbdeb",
"MacAddress": "02:42:ac:11:00:04",
"IPv4Address": "172.17.0.4/16",
"IPv6Address": ""
},
"94cd188b01ea1bb3472a7c192c9edf4e322da03691baa99080ddede880fba7bf": {
"Name": "tomcat02",
"EndpointID": "d9f7a82132ea3d467eba5434a7b5912b667f7670b331dc59f0237c96dd4730bf",
"MacAddress": "02:42:ac:11:00:03",
"IPv4Address": "172.17.0.3/16",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.bridge.default_bridge": "true",
"com.docker.network.bridge.enable_icc": "true",
"com.docker.network.bridge.enable_ip_masquerade": "true",
"com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
"com.docker.network.bridge.name": "docker0",
"com.docker.network.driver.mtu": "1500"
},
"Labels": {}
}
]
可以使用docker network create
命令创建自定义网络:
docker network create \
--driver bridge \
--subnet 192.168.0.0/16 \
--gateway 192.168.0.1 \
mynet
在这里创建了自定义网络mynet
,网络模式为桥接模式,网段为192.168.0.0/16
,网关为192.168.0.0/16
在该网络下创建2个容器tomcat-mynet01
和tomcat-mynet02
:
docker run -d -P --name tomcat-mynet01 --net mynet tomcat
docker run -d -P --name tomcat-mynet02 --net mynet tomcat
在自定义网络内,可以使用容器名或容器id访问网络内的其它容器.
docker exec -it tomcat-mynet01 ping 192.168.0.3 # 在容器tomcat-mynet01内ping容器tomcat-mynet02的IP地址,能ping通
docker exec -it tomcat-mynet01 ping tomcat-mynet02 # 在容器tomcat-mynet01内ping容器tomcat-mynet02的容器名,能ping通
docker exec -it tomcat-mynet01 ping 13251b560879 # 在容器tomcat-mynet01内ping容器tomcat-mynet02的容器id,能ping通
docker exec -it tomcat-mynet02 ping 192.168.0.2 # 在容器tomcat-mynet02内ping容器tomcat-mynet01的IP地址,能ping通
docker exec -it tomcat-mynet02 ping tomcat-mynet01 # 在容器tomcat-mynet02内ping容器tomcat-mynet01的容器名,能ping通
docker exec -it tomcat-mynet02 ping da45a19c956e # 在容器tomcat-mynet02内ping容器tomcat-mynet01的容器id,能ping通
Docker的不同网络之间默认是隔离的,可以使用docker network connect
命令将容器和网络连通,格式如下:
docker network connect [OPTIONS] NETWORK CONTAINER
-
上文创建的
mynet
网络和默认的docker0
网络是隔离的,两个网络之间的容器不能互相访问docker exec -it tomcat01 ping 192.168.0.3 # 在容器tomcat01内ping容器tomcat-mynet01的IP地址,不能ping通 docker exec -it tomcat01 ping tomcat-mynet01 # 在容器tomcat01内ping容器tomcat-mynet01的容器名,不能ping通 docker exec -it tomcat01 ping da45a19c956e # 在容器tomcat01内ping容器tomcat-mynet01的容器id,不能ping通
-
使用
docker network connect
命令将tomcat01
连接到mynet
网络上docker network connect mynet tomcat01
使用
docker network inspect mynet
命令查看mynet
网络的详细信息,可以看到mynet
网络下包含了tomcat01
容器.[ { "Name": "mynet", "Id": "203a1e68d66172078f437d23f146dd1309592dcd6eab87a7d4461aea119bdb08", // ... "Containers": { // 可以看到容器中除了原有的tomcat-mynet01和tomcat-mynet02容器之外,又增加了tomcat01容器 "da45a19c956e881c4c445e49b301f2bcbdf0e388bc29925da080dfab19560b21": { "Name": "tomcat-mynet01", "EndpointID": "da7a5a3a5e424b63359ec13637743a97359bb5ba5461aacab115f14687001bed", "MacAddress": "02:42:c0:a8:00:02", "IPv4Address": "192.168.0.2/16", "IPv6Address": "" }, "13251b5608793d7fd4ee16aae520fb9d45846f122da351438eaac0565591c438": { "Name": "tomcat-mynet02", "EndpointID": "f78746e31a9d691625b271ee4ebc63774bac1a8c93956e894e494b175cbcf19a", "MacAddress": "02:42:c0:a8:00:03", "IPv4Address": "192.168.0.3/16", "IPv6Address": "" }, "3c35894e46aedb420bf560a9db4f393012a999ba99dc5679077ee3ac8113b7b4": { "Name": "tomcat01", "EndpointID": "e5d23183e8ad0454c8e8de7434f6480ee304dfeb2556b64bf7b8c7591a7983bb", "MacAddress": "02:42:c0:a8:00:04", "IPv4Address": "192.168.0.4/16", "IPv6Address": "" }, }, // ... } ]
查看容器
tomcat01
的IP地址可以看到,容器tomcat01
有两个IP地址:docker exec -it tomcat01 ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever 136: eth0@if137: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0 valid_lft forever preferred_lft forever 148: eth1@if149: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:c0:a8:00:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 192.168.0.4/16 brd 192.168.255.255 scope global eth1 valid_lft forever preferred_lft forever
-
容器
tomcat01
可以和mynet
下的容器进行通信了:docker exec -it tomcat01 ping 192.168.0.3 # 在容器tomcat01内ping容器tomcat-mynet01的IP地址,不能ping通 docker exec -it tomcat01 ping tomcat-mynet01 # 在容器tomcat01内ping容器tomcat-mynet01的容器名,不能ping通 docker exec -it tomcat01 ping da45a19c956e # 在容器tomcat01内ping容器tomcat-mynet01的容器id,不能ping通
练习1: 部署一个Redis集群
-
创建
redis
网络:docker network create redis --subnet 172.38.0.0/16
-
启动6个Redis容器,这里使用脚本生成命令并运行:
创建6个Redis节点的配置文件
# 通过脚本创建六个redis配置 for port in $(seq 1 6); do mkdir -p /mydata/redis/node-${port}/conf touch /mydata/redis/node-${port}/conf/redis.conf cat << EOF >/mydata/redis/node-${port}/conf/redis.conf port 6379 bind 0.0.0.0 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-announce-ip 172.38.0.1${port} cluster-announce-port 6379 cluster-announce-bus-port 16379 appendonly yes EOF done
创建6个Redis镜像
for port in $(seq 1 6); do docker run \ -p 637"${port}":6379 -p 1637"${port}":16379 \ --name redis-"${port}" \ -v /mydata/redis/node-"${port}"/data:/data \ -v /mydata/redis/node-"${port}"/conf/redis.conf:/etc/redis/redis.conf \ -d \ --net redis --ip 172.38.0.1"${port}" \ redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf; done
-
进入其中一个容器,创建集群
# 进入容器redis-1 docker exec -it redis-1 /bin/bash # 创建集群 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
-
查看集群信息
# 连接集群 redis-cli -c # 查看集群信息 cluster info # 查看节点 cluster nodes
练习2: 将SpringBoot项目打包成Docker镜像
-
使用IDEA创建一个SpringBoot项目:
创建项目后创建
helloController.java
文件如下:@RestController public class HelloController { @GetMapping("/hello") public String hello(){ return "hello world"; } }
使用MAVEN将项目打包成jar包
app.jar
-
在项目下编写Dockerfile如下:
FROM java:8 # 服务器只有dockerfile和jar在同级目录 COPY *.jar /app.jar CMD ["--server.port=8080"] # 指定容器内要暴露的端口 EXPOSE 8080 ENTRYPOINT ["java","-jar","/app.jar"]
-
将Dockerfile和项目的jar包上传到linux服务器上,构建镜像并启动容器:
# 构建镜像 docker build -t my-app . # 启动容器 docker run -d -p 8080:8080 --name my-app my-app
-
测试访问:
curl localhost:32779/hello
输出:
hello world
pdf版本笔记的下载地址: Docker学习笔记01(访问密码:3834)