本文是我学习Docker的过程及笔记,主要是根据以下课程的视频教程或文章进行学习,在此对这些作者致最崇高的敬意:

前言

前置知识: Linux 基本命令、 SpringBoot(如果会是最好的)
Docker 是一个 Java 高级工程师的基本技能,用于集群部署

概述

Docker出现的背景: 开发环境、测试环境、正式不一致,引发其他问题,如果是集群部署,每个机器都要部署环境会很麻烦费时费力而且还很容易出错,而且这些环境并不是跨平台的。所以需要将项目的运行和环境打包在一起,然后部署到机器上。

传统部署项目: 开发jar包,运维来部署。
现在部署项目:开发打包并部署上线。

java -> 打包成apk -> 发布到应用商店 -> 用户下载apk -> 安装即可运行。
java -> 打包成jar+环境(也叫docker镜像) -> 将docker镜像放到Docker仓库(等同于镜像商店) -> 下载我们发布的docker镜像 -> 直接运行镜像。

Docker 思想来源于集装箱,打包装箱代码和环境成docker镜像,将多个docker镜像隔离,互不影响。
Docker 可以通过隔离机制,可以将服务器资源利用到极致。

Docker 历史

开源: 开放源代码。
虚拟机: 虚拟化技术,模拟完整的系统,启动时间几分钟,大小几个G。
Docker 容器技术: 也是一种虚拟化技术,最核心的系统环境,启动时间秒级,大小几个Mb,甚至kb级。
Docker 是基于Go语言实现的云开源项目,它的文档超级详细。

Docker 的理念

Docker的主要目标是“Build,Ship and Run Any App , Anywhere”,也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的APP(可以是一个WEB应用或数据库应用等等)及其运行环境能够做到“一次封装,到处运行”。
Linux 容器技术的出现就解决了这样一个问题,而 Docker 就是在它的基础上发展过来的。将应用运行在 Docker 容器上面,而 Docker 容器在任何操作系统上都是一致的,这就实现了跨平台、跨服务器。只需
要一次配置好环境,换到别的机子上就可以一键部署好,大大简化了操作。

Docker 能做什么

虚拟机技术:
image.png
缺点:

  1. 资源占用多
  2. 冗余步骤多
  3. 启动慢

容器虚拟化技术
由于前面虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术: Linux 容器(Linux Containers,缩写为 LXC)。
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。有了容器,就可以将软件运行所需的所有资源打包到一个隔离的容器中。容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行。
image.png

比较 Docker 和传统虚拟化方式的不同之处

  • 传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;
  • 而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
  • 每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源。

开发/运维(DevOps)

  • 更快速的应用交付和部署:
    • 传统的应用开发: 需给运维提供一堆安装程序和配置说明文档,安装部署后需根据配置文档进行繁杂的配置才能正常运行。
    • Docker化之后: 只需交付少量容器镜像文件,在正式生产环境加载镜像并运行即可,应用安装配置在镜像里已经内置好,大大节省部署配置和测试验证时间。
  • 更便捷的升级和扩缩容:
    随着微服务架构和Docker的发展,大量的应用会通过微服务方式架构,应用的开发构建将变成搭乐高积木一样,每个Docker容器将变成一块“积木”,应用的升级将变得非常容易。当现有的容器不足以支撑业务处理时,可通过镜像运行新的容器进行快速扩容,使应用系统的扩容从原先的天级缩短为分钟级,甚至秒级。
  • 更简单的系统运维:
    应用容器化运行后,生产环境运行的应用可与开发、测试环境的应用高度一致,容器会将应用程序相关的环境和状态完全封装起来,不会因为底层基础架构和操作系统的不一致性给应用带来影响,产生新的BUG。当出现程序异常时,也可以通过测试环境的相同容器进行快速定位和修复。
  • 更高效的计算资源利用:
    Docker是内核级虚拟化,其不像传统的虚拟化技术一样需要额外的Hypervisor [管理程序] 支持,所以在一台物理机上可以运行很多个容器实例,可大大提升物理服务器的CPU和内存的利用率。

Docker官网: http://www.docker.com
Docker中文网站: https://www.docker-cn.com
Docker Hub官网: https://hub.docker.com (仓库)

Docker 的安装

Docker 的基本组成

Docker 的架构图
image.png

  • Docker: 是一个容器运行的载体,或称之为管理引擎。
  • 镜像(image): 我们把应用程序和配置依赖打包好形成一个可交付的运行环境,这个打包好的运行环境就叫 image 镜像文件。只有通过这个镜像文件才能生成 Docker 容器,image 文件可以看作是容器的模板。可以用镜像来创建 Docker 容器(的实例),一个镜像可以生成/创建多个同时运行的容器实例。 就好似 Java 中的 类和对象,类就是镜像,容器就是 new 出来的对象。
  • 容器(container): 容器是用镜像创建的运行实例,用于独立运行的一个或一组应用。 它可以被启动、开始、停止、删除。每个容器都是相互隔离的,容器之间没有任何接口,保证安全。 可以把容器看做是一个简易版的 Linux 环境(包括root用户权限、进程空间、用户空间和网络空间等) + 运行在里面的应用程序。
  • 仓库(Repository): 就是集中存放镜像文件的地方,我们可以把镜像发布到仓库,需要的时候从仓库把镜像拉下来。仓库(Repository)和仓库注册服务器(Registry)是有区别的。仓库注册服务器上往往存放着多个仓库,每个仓库中又包含了多个镜像,每个镜像有不同的标签(tag)。 仓库分为公开仓库(Public)和私有仓库(Private)两种形式。 最大的公开仓库是 DockerHub,存放了数量庞大的镜像供用户下载。 国内的公开仓库包括阿里云、网易云等。

本课程环境说明

狂神使用的是 CentOS 7 (64-bit)
目前,CentOS 仅发行版本中的内核支持 Docker。
Docker 运行在 CentOS 7 上,要求系统为64位、系统内核版本为 3.10 以上。

查看自己的内核:
uname -r 命令用于打印当前系统相关信息(内核版本号、硬件架构、主机名称和操作系统类型等)。

[root@kuangshen ~]# uname -r 
3.10.0-1062.12.1.el7.x86_64

查看版本信息:
cat /etc/os-release

[root@kuangshen ~]# 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"

安装步骤

CentOS 安装 Docker

  1. 官网安装参考手册: https://docs.docker.com/engine/install/centos/
  2. 确定你是CentOS7及以上版本
  3. yum安装gcc相关环境(需要确保 虚拟机可以上外网 )
# CentOS
yum -y install gcc
yum -y install gcc-c++
  1. 卸载旧版本
# CentOS
yum remove docker \
  docker-client \
  docker-client-latest \
  docker-common \
  docker-latest \
  docker-latest-logrotate \
  docker-logrotate \
  docker-engine
  1. 安装需要的软件包
yum install -y yum-utils
  1. 设置镜像仓库
# 一定要用国内的镜像仓库: 推荐使用国内的镜像仓库,这里以阿里云为例
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

# 如果用国外的镜像仓库,会被墙,而报错
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
[Errno 14] curl#35 - TCP connection reset by peer
[Errno 12] curl#35 - Timeout
  1. 更新yum软件包索引
yum makecache fast
  1. 安装 Docker CE
yum install docker-ce docker-ce-cli containerd.io
  1. 启动 Docker
systemctl start docker
  1. 测试 Docker 命令
docker version
docker run hello-world
docker imges # 查看镜像列表,能看到刚刚下载的 hello-world 镜像
  1. 卸载 Docker
systemctl stop docker

# 卸载
yum -y remove docker-ce docker-ce-cli containerd.io
# Debian/Ubuntu使用以下命令:
apt uninstall docker-ce docker-ce-cli containerd.io

rm -rf /var/lib/docker
  1. 重启 Docker
systemctl restart docker.service

Debian/Ubuntu 安装 Docker

  1. 官网安装参考手册: https://docs.docker.com/engine/install/debian/
  2. apt安装gcc相关环境(需要确保 虚拟机可以上外网 )
sudo apt install gcc
sudo apt install aptitude
sudo aptitude install gcc g++
  1. 卸载旧版本
apt remove docker \
  docker-client \
  docker-client-latest \
  docker-common \
  docker-latest \
  docker-latest-logrotate \
  docker-logrotate \
  docker-engine

apt-get remove docker docker-engine docker.io containerd runc
  1. 使用国内 daocloud 一键安装命令
curl -sSL https://get.daocloud.io/docker | sh
  1. 测试是否安装成功
# 命令执行
docker version
docker run hello-world 
docker imges # 查看镜像列表,能看到刚刚下载的 hello-world 镜像

# 如果执行 `docker run hello-world` 时输出以下信息说明安装成功
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete                                                                                                                                  Digest: sha256:c3b4ada4687bbaa170745b3e4dd8ac3f194ca95b2d0518b417fb47e5879d9b5f
Status: Downloaded newer image for hello-world:latest


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/
  1. 设置 Docker 的镜像源为国内
vi /etc/docker/daemon.json

# 写入以下内容并保存
{
  "registry-mirrors": [
    "https://docker.mirrors.ustc.edu.cn",
    "https://registry.docker-cn.com",
    "http://hub-mirror.c.163.com"
  ]
}

# {
#   "registry-mirrors": [
#     "https://docker.mirrors.ustc.edu.cn",
#     "https://registry.docker-cn.com",
#     "http://hub-mirror.c.163.com",  # 注意这里不能有逗号,否则在下面重启时会报错: "Failed to start Docker Application Container Engine."
#   ]
# }
# 如果报错 "Failed to start Docker Application Container Engine." ,则使用以下命令查看错误日志。发现 ] 附近有错误,发现是多了个逗号无法解析成 json (python 用多了,哭..),把 /etc/docker/daemon.json 中的 ] 的前一个逗号删除即可
root@fucaijin:~# dockerd --log-level error
unable to configure the Docker daemon with file /etc/docker/daemon.json: invalid character ']' looking for beginning of value

# Debian 重启 docker
systemctl daemon-reload
systemctl restart docker

# 查看所有容器
docker ps -a
  1. 设置镜像仓库
    内容待编写

  2. 启动 Docker

# 启动 Docker 服务并设置开机启动
sudo systemctl start docker
sudo systemctl enable docker
  1. 重启 Docker
systemctl daemon-reload
systemctl restart docker
  1. (可选)安装 docker-compose
    使用 sudo pip install docker-compose 安装。或使用下面的方式进行安装
# 测试是否已安装,如果弹出版本号就是已安装
docker-compose version

# 下载
curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.5/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

# 授权
sudo chmod +x /usr/local/bin/docker-compose

# 测试是否安装成功
cd /usr/local/bin
docker-compose version # 弹出版本号就是安装成功
  1. 卸载 Docker
systemctl stop docker
apt purge docker-ce docker-ce-cli containerd.io
apt uninstall docker-ce docker-ce-cli containerd.io # 执行了上面那行,这行不执行成功也没事

# 删除镜像、容器、配置文件等内容
rm -rf /var/lib/docker

配置阿里云镜像加速

  1. 介绍: https://www.aliyun.com/product/acr
  2. 注册一个属于自己的阿里云账户(可复用淘宝账号)
  3. 进入管理控制台设置密码,开通
  4. 查看自己的镜像加速器
    image.png
  5. 配置镜像加速
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://qiyb9988.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

测试 HelloWorld

执行docker run hello-world
docker run 命令的执行流程:
image.png

Docker 的底层原理

Docker 是怎么工作的
Docker 是一个 Client-Server 结构的系统, Docker 守护进程运行在主机(服务器)上, 然后通过 Socket 连接从客户端访问,客户端的命令发送到主机(服务器)的守护进程后,就可以管理或运行在主机(服务器)上的容器。
image.png

为什么 Docker 比 VM (虚拟机)快

  1. docker 有着比虚拟机更少的抽象层。由亍 docker 不需要 Hypervisor 实现硬件资源虚拟化,运行在 docker 容器上的程序直接使用的都是实际物理机的硬件资源。因此在 CPU 、内存利用率上 docker 将会在效率上有明显优势。
  2. docker利用的是宿主机的内核,而不需要Guest OS。因此,当新建一个容器时,docker不需要和虚拟机一样重新加载一个操作系统内核。仍而避免引寻、加载操作系统内核返个比较费时费资源的过程,当新建一个虚拟机时,虚拟机软件需要加载 Guest OS ,返回新建过程是分钟级别的。而 docker 由于直接利用宿主机的操作系统,则省略了返个过程,因此新建一个 docker 容器只需要几秒钟。
    image.png
    image.png

Docker常用命令

帮助命令

docker version # 显示 Docker 版本信息。
docker info # 显示 Docker 系统信息,包括镜像和容器数。。
docker --help # 帮助

image.png
帮助文档地址: https://docs.docker.com/engine/reference/commandline/build/

镜像命令

docker images

# 列出本地主机上的镜像
[root@kuangshen ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest bf756fb1ae65 4 months ago 13.3kB

# 信息字段说明
REPOSITORY: 镜像的仓库源
TAG: 镜像的标签
IMAGE ID: 镜像的ID
CREATED: 镜像创建时间
SIZE: 镜像大小

# 同一个仓库源可以有多个 TAG ,代表这个仓库源的不同版本,我们使用REPOSITORY: TAG 定义不同的镜像,如果你不定义镜像的标签版本,docker 将默认使用 lastest 镜像。

# docker命令的可选参数
-a 或 --all:  列出本地所有镜像
-q 或 --quiet:  只显示镜像id
-aq : 显示所有镜像
--digests:  显示镜像的摘要信息

搜索镜像 docker search

# 搜索镜像
[root@kuangshen ~]# docker search mysql
NAME DESCRIPTION STARS OFFICIAL
mysql MySQL is a widely used, open-source relation… 9484
[OK]

# docker search 某个镜像的名称 对应 DockerHub 仓库中的镜像
# 也可以使用 docker search --help 来查看 docker search 这个命令参数和说明

# 可选项
--filter=STARTS=50 :  列出收藏数不小于50的镜像。

下载镜像 docker pull

# 下载镜像
[root@kuangshen ~]# docker pull mysql
Using default tag: latest # 不写tag,默认是latest(最新版)
latest: Pulling from library/mysql
54fec2fa59d0: Already exists # 分层下载: docker image的核心 联合文件系统
bcc6c6145912: Already exists
05de4d0e206e: Already exists
...
c4a3851d9207: Pull complete
82a1cc65c182: Pull complete
Digest:
sha256:61a2a33f4b8b4bc93b7b6b9e65e64044aaec594809f818aeffbff69a893d1944 # 镜像签名
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest # 镜像的真实位置

# 所以以下两行命令是等价的
docker pull mysql
docker pull docker.io/library/mysql:latest


# 下载指定的镜像版本
[root@kuangshen ~]# docker pull mysql:5.7 # 注意:该版本在镜像仓库中一定要存在,否则会报错
...

删除镜像 docker rmi

# 删除镜像
docker rmi -f 镜像id # 删除单个
docker rmi -f 镜像id 镜像id # 删除多个镜像
docker rmi -f 镜像名:tag 镜像名:tag # 删除多个镜像
docker rmi -f $(docker images -qa) # 删除全部镜像:查出所有镜像然后遍历删除

容器命令

说明: 有镜像才能创建容器,我们这里使用 centos 的镜像来虚拟出一个 centos 看看

# 拉取镜像
docker pull centos

新建容器并启动

# 启动容器命令
docker run [OPTIONS] IMAGE [COMMAND][ARG...]

# OPTIONS: 可选的常用参数说明
--name="Name" # 给容器指定一个名字,比如tomcat1,tomcat2,... 
-d # 后台方式运行容器,并返回容器的id
-i # 以交互模式运行容器,通过和 -t 一起使用。命令就是 -it
-t # 给容器重新分配一个终端,通常和 -i 一起使用
-P # 指定容器的端口(大写),比如 "-p 8080:8080",
  # 写法1: -p ip:主机端口:容器端口 
  # 写法2: -p 主机端口:容器端口 (用的多)
  # 写法3: -p 容器端口
-p # 随机指定端口映射(小写),一般可以有四种写法


# 测试
# 1. 查看容器
[root@kuangshen ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
centos latest 470671670cac 3 months ago 237MB

# 2. 启动容器并进入容器: 使用centos进行用交互模式启动容器,然后在容器内执行/bin/bash命令
[root@kuangshen ~]# docker run -it centos /bin/bash
[root@dc8f24dd06d0 /]# ls # 注意地址,已经切换到容器内部了,此时执行的命令都是在容器内部执行的
bin etc lib lost+found mnt proc run srv tmp var
dev home lib64 media opt root sbin sys usr

[root@dc8f24dd06d0 /]# exit # 使用 exit 停止容器的运行并退出,然后回到主机
exit

列出所有运行的容器

# 命令
docker ps [OPTIONS]

# 常用参数说明
-a # 列出当前所有正在运行的容器 + 历史运行过的容器
-l # 显示最近创建的容器
-n=8 # 显示最近8个创建的容器
-q # 静默模式,只显示容器编号。

退出容器

# 在容器里执行:
exit # 停止容器并退出
Ctrl+P+Q # 容器不停止退出

删除容器

docker rm 容器id # 删除指定容器
docker rm $(docker ps -aq) # 删除所有非运行中的容器
docker rm -f $(docker ps -aq) # 强制删除所有容器(运行中的容器也会被删除)
docker ps -aq|xargs docker rm # 先查出所有容器,然后执行删除容器命令

启动停止容器

docker start (容器id or 容器名) # 启动容器
docker restart (容器id or 容器名) # 重启容器
docker stop (容器id or 容器名) # 停止容器
docker kill (容器id or 容器名) # 强制停止容器

常用其他命令

启动容器在后台运行

# 命令
docker run -d 容器名

# 例子
docker run -d centos # 启动centos,使用后台方式启动。但会发现启动之后,使用docker ps 命令查看,却发现该容器没有在运行。看↓

# 问题:  使用docker ps 查看,发现容器已经退出了。
# 对于该问题的说明: Docker容器后台运行,就必须有一个前台进程,容器运行的命令如果不是那些一直挂起的命令,就会自动退出。

# 比如,你运行了nginx服务,但是docker前台没有运行应用,这种情况下,容器启动后,会立即自杀,因为他觉得没有程序在运行了,所以最好的情况是,将你的应用使用前台进程的方式运行启动。

查看日志

# 查看 docker 的日志信息
docker logs -f -t --tail 容器id

# 查看 docker logs 命令的帮助信息
docker logs --help

# 例子: 我们启动 centos这个容器,并在容器里执行命令`while true;do echo kuangshen;sleep 1;done`,再查看日志
[root@kuangshen ~]# docker run -d centos /bin/sh -c "while true;do echo kuangshen;sleep 1;done"

[root@kuangshen ~]# docker ps
CONTAINER ID IMAGE
c8530dbbe3b4 centos

# -t 显示时间戳
# -f 打印最新的日志
# --tail 20 要显示的日志20条
[root@kuangshen ~]# docker logs -tf --tail 10 c8530dbbe3b
2020 -05-11T08:46:40.656901941Z kuangshen
2020 -05-11T08:46:41.658765018Z kuangshen
...

查看容器中运行的进程信息,支持 ps 命令参数

# 命令
docker top 容器id

# 测试
[root@kuangshen ~]# docker top c8530dbbe3b
UID PID PPID C STIME TTY TIME CMD
root 27437 27421 0 16 :43? 00 :00:00 /bin/sh -c ....

[非常重要]查看容器/镜像的元数据

# 命令格式: "docker inspect <容器id>"

# 测试
[root@kuangshen ~]# docker inspect c8530dbbe3b

[
  {
  "Id":
  "c8530dbbe3b44a0c873f2566442df6543ed653c1319753e34b400efa05f77cf8", # 这id是容器的完整的id,我们平时看到的只是是截取的这个id前几位
  "Created": "2020-05-11T08:43:45.096892382Z",
  "Path": "/bin/sh",
  "Args": [
  "-c",
  "while true;do echo kuangshen;sleep 1;done"
  ],
  # 状态
  "State": {
    "Status": "running",
    "Running": true,
    "Paused": false,
    "Restarting": false,
    "OOMKilled": false,
    "Dead": false,
    "Pid": 27437 ,
    "ExitCode": 0 ,
    "Error": "",
    "StartedAt": "2020-05-11T08:43:45.324474622Z",
    "FinishedAt": "0001-01-01T00:00:00Z"
  },
  // ...........
]

进入正在运行的容器
我们通常都是在后台运行容器,如果需要进入容器,有两种方式

# 进入容器的方式1: 它进入的容器后,不会打开容器中正在运行的命令行终端,而是会开启一个新的命令行窗口,即会在容器里启动新的进程
# 命令格式: "docker exec -it <容器id> /bin/bash"
[root@kuangshen ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c8530dbbe3b4 centos "/bin/sh -c 'while true;do echo kuangshen;sleep 1;done" 12 minutes ago Up 12 minutes happy_chaum

[root@kuangshen ~]# docker exec -it c8530dbbe3b4 /bin/bash

[root@c8530dbbe3b4 /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
... ...

# 进入容器的方式2: 它进入的容器后,会进入容器中正在运行的命令行,即不会启动新的进程
# 命令格式: "docker attach <容器id>"
[root@kuangshen ~]# docker exec -it c8530dbbe3b4 /bin/bash

[root@c8530dbbe3b4 /]# ps -ef
UID PID PPID C STIME TTY TIME CMD
... ...

# 区别
# exec 是在容器中打开新的终端,并且可以启动新的进程
# attach 直接进入容器启动命令的终端,不会启动新的进程

从容器内拷贝文件到主机上

# 从容器拷贝文件到主机命令
# 命令格式: "docker cp <容器id>:<容器内路径> <目的主机路径>"

# 测试
# 进入容器内创建一个文件测试 HelloWord.java 
[root@kuangshen ~]# docker attach c8530dbbe3b4
[root@c8530dbbe3b4 /]# cd /home
[root@c8530dbbe3b4 home]# touch HelloWord.java
[root@c8530dbbe3b4 home]# ls
HelloWord.java

[root@c8530dbbe3b4 home]# exit # 退出容器
exit

# 拷贝文件f1到主机(容器没有在运行也可以执行)
[root@kuangshen ~]# docker cp c8530dbbe3b4:/home/HelloWord.java /home
[root@kuangshen ~]# cd /home
[root@kuangshen home]# ls
HelloWord.java

# docker 的常用命令(需要全部命令敲一遍进行练习)
attach 容器id # 当前 shell 下 attach 连接指定运行镜像
build # 通过 Dockerfile 创建镜像
commit # 提交当前容器为新的镜像
cp #从容器中拷贝指定文件或者目录到宿主机中
create # 创建一个新的容器,同 run,但不启动容器
diff # 查看 docker 容器变化
events # 从 docker 服务获取容器实时事件
exec # 在已存在的容器上运行命令
export # 将容器保存为一个 tar 压缩文件[相当于备份。对应命令是 import ,将压缩的 tar 文件还原为一个容器]
import # 从tar包中的内容创建一个新的文件系统映像[对应export]
history # 展示一个镜像形成历史
images # 列出系统当前镜像
info # 显示系统相关信息
inspect # 查看容器详细信息
kill # 强制停止一个容器
save # 保存一个镜像为一个 tar 包[对应 load]
load # 从一个 tar 包中加载一个镜像[对应 save]
login # 注册或者登陆一个 docker 源服务器
logout # 从当前 Docker registry 退出
logs # 输出当前容器日志信息
port # 查看映射端口对应的容器内部源端口
pause # 暂停容器
ps # 列出容器列表
pull # 从docker镜像源服务器拉取指定镜像或者库镜像
push # 推送指定镜像或者库镜像至docker源服务器
restart # 重启运行的容器
rm # 移除一个或者多个容器
rmi # 移除一个或多个镜像[无容器使用该镜像才可删除,否则需删除相关容器才可继续或 -f 强制删除]
run # 创建一个新的容器并运行一个命令
search # 在 docker 仓库中搜索镜像
start # 启动容器
stop # 停止容器
tag # 给镜像打标签(一般的标签都是版本号)
top # 查看容器中运行的进程信息
unpause # 取消暂停容器
version # 查看 docker 版本号
wait # 截取容器停止时的退出状态值

image.png

作业练习

使用 Docker 安装 Nginx

# 1. 搜索镜像(也可以去网站收拾)
[root@kuangshen ~]# docker search nginx
NAME DESCRIPTION STARS OFFICIAL
nginx Official build of Nginx. 13159 [OK]

# 2. 拉取镜像
[root@kuangshen ~]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
54fec2fa59d0: Pull complete
4ede6f09aefe: Pull complete
f9dc69acb465: Pull complete
Digest:
sha256:86ae264c3f4acb99b2dee4d0098c40cb8c46dcf9e1148f05d3a51c4df6758c
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
# 列出本地镜像
[root@kuangshen ~]# docker images
...

# 3. 启动容器
[root@kuangshen ~]# docker run -d --name mynginx -p 3500:80 nginx
# 将主机的3500映射到容器的80,也就是访问服务器的3500端口,就会访问到容器的80端口,mynginx是自定义的容器名称。-p 就是暴露/映射端口的意思
a95d5f2f057fc609082cfa0de906bd690f95c43a26d38420d081f0e255b232ec

[root@kuangshen ~]# docker ps
CONTAINER ID IMAGE PORTS NAMES
a95d5f2f057f nginx 0 .0.0.0:3500->80/tcp mynginx

# 4. 测试访问:访问本地的3500就会访问docker的容器80端口
[root@kuangshen ~]# curl localhost:3500
<html>
  <title>Welcome to nginx!</title> # ok
  ....
</html>

# 5. 进入容器,
[root@kuangshen ~]# docker exec -it mynginx /bin/bash
root@a95d5f2f057f:/# whereis nginx # 寻找nginx
nginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx
root@a95d5f2f057f:/# cat /usr/share/nginx/html/index.html # nginx 的首页路径
<!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>
</html>

思考: 如果每次需要修改容器内的nginx配置文件,都需要进入容器,会很麻烦,如果能在容器外提供一个映射路径,实现在容器外部修改文件,容器内部的nginx配置文件也自动修改,那不是很方便?
继续往下看

使用 docker 安装 tomcat

# 官方文档解释
# -it : 交互模式
# --rm: 容器启动成功后,等退出容器后就自动移除容器、镜像,一般在测试情况下使用。相当于每次启动容器都是使用一个干净的系统,类似于网吧的电脑,安装软件后只要关闭电脑数据都会被格式化。

docker run -it --rm tomcat:9.0

# 1. 下载tomcat镜像
docker pull tomcat

# 2. 启动容器
docker run -d -p 1234:8080 --name tomcat9 tomcat
# 此时访问服务器的额1234端口发现nginx已经起来了但是没法正常访问

# 3. 进入tomcat9这个容器的终端命令
docker exec -it tomcat9 /bin/bash

# 发现问题:
# 1. 容器内有些Linux 命令缺失,无法执行。
# 2. 容器内的tomcat目录没有webapps目录,因为阿里云镜像的原因,下载的镜像默认是简化后的最小镜像,只是保证最小运行环境,解决办法是把tomcat目录内的webapps.dist目录复制或改名为webapps即可

使用 docker 部署 es + kibana

# 我们启动es这种容器需要考虑几个问题
# 1. es暴露的端口很多的问题 9200、9300 
# 2. es的数据一般需要放置到安全目录: 数据卷的挂载问题 data、plugins、conf
# 3. es十分吃内存,给es配置内存限制: - "ES_JAVA_OPTS=-Xms512m -Xmx512m"

# 扩展命令
docker stats 容器id # 查看容器的cpu内存和网络状态

# 1. 下载es镜像、并启动,启动之后会很卡,继续往下看
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.6.2

# 2. 启动之后很卡,使用命令 `docker stats` 查看cpu状态 ,发现 cpu 的占用的很大,而且很卡

# 3. 测试访问,如果没报错并且有信息输出,说明已经启动成功
[root@kuangshen data]# curl localhost:9200
...

# 4. 给es增加内存限制启动
docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms64m -Xmx512m" elasticsearch:7.6.2

# 5. 启动之后,再次使用命令 `docker stats` 查看cpu状态,发现限制了es内存后,占用服务器的资源就不高了

# 6. 测试访问,效果一样,ok。
[root@kuangshen data]# curl localhost:9200

# 思考作业: 如果我们要使用 kibana , 如果配置连接上我们的es呢?网络该如何配置呢?需要学习Docker网络原理

image.png

可视化面板

可视化面板平时不用,玩玩即可
Portainer是Docker的图形化管理工具,提供状态显示面板、应用模板快速部署、容器镜像网络数据卷的基本操作(包括上传下载镜像,创建容器等操作)、事件日志显示、容器控制台操作、Swarm集群和服务等集中管理和操作、登录用户管理和控制等功能。功能十分全面,基本能满足中小型单位对容器管理的全部需求。
如果仅有一个docker宿主机,则可使用单机版运行,Portainer单机版运行十分简单,只需要一条语句即可启动容器,来管理该机器上的docker镜像、容器等数据。

Portainer(先用这个)

# 安装Portainer:
docker run -d -p 8088:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer

Rancher(CI/CD再用这个)

#安装rancher-server
docker run --name rancher-server -p 8000 :8080 -v /etc/localtime:/etc/localtime:ro -d rancher/server

#安装agent
docker run --rm --privileged -v /var/run/docker.sock:/var/run/docker.sock -v /var/lib/rancher:/var/lib/rancher rancher/agent:v1.2.http://39.101.191.131:8000/v1/scripts/D3DBD43F263109BB881F:1577750400000:7M0y
BzCw4XSxJklD7TpysYIpI

如果使用阿里云,需要先开放安全组的端口 8088
访问可视化面板: http://服务器ip:8088
首次登陆需要注册账号。
界面如下图:
image.png
单机版这里选择local即可,选择完毕,点击Connect即可连接到本地docker:
image.png
登录成功:
image.png

Docker镜像讲解

镜像是什么

镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时、库、环境变量和配置文件。

如何得到镜像

  • 从远程仓库下载
  • 朋友拷贝给你
  • 自己制作镜像DockerFile

Docker镜像加载原理

UnionFS(联合文件系统): Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加(比如第一层操作是安装了centos,第二层是按照了docker,第三层是安装了jdk...。每层操作都会被记录下来,下载镜像时显示的一层层的就是这些操作。比如第一个下载的镜像的第一层操作是"安装Linux内核",如果下载第二个镜像也有"安装Linux内核"这一层,此时就不用下载这一层了,和第一个共用即可,所以可以极大节省内存、空间),同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
UnionFS(联合文件系统)的特性: 一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
image.png

Docker镜像加载原理

docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统叫UnionFS。

  • bootfs(boot file system): 主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
    加载系统: 从黑屏到开机进入系统,都需要到加载器bootloader,加载器使用完了之后,就可以就可以把加载器卸载了。这一部分的逻辑无论是什么镜像都是可以共用的。
  • rootfs(root file system): 在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。

平时我们安装进虚拟机的CentOS都是好几个G,为什么Docker这里才200M
因为对于一个精简的OS,rootfs 可以很小,只需要包含最基本的命令,工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供rootfs就可以了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以共用bootfs。所以虚拟机是分钟级别的启动,docker是秒级启动。

分层理解

分层的镜像

我们可以去下载一个镜像,注意观察下载的日志输出,可以看到是一层一层的在下载: docker pull redis
image.png

思考: 为什么Docker镜像要采用这种分层的结构呢?
最大的好处,我觉得莫过于是资源共享了。比如有多个镜像都从相同的Base镜像构建而来,那么宿主机只需在磁盘上保留一份base镜像,同时内存中也只需要加载一份base镜像,这样就可以为所有的容器服务了,而且镜像的每一层都可以被共享。

可以通过 docker image inspect 镜像名:镜像版本 查看镜像分层:

[root@kuangshen home]# docker image inspect redis:latest
[
// .....
  "RootFS": {
    "Type": "layers",
    "Layers": [ // 每一层对应每一个操作步骤
      "sha256:c2adabaecedbda0af72b153c6499a0555f3a769d52370469d8f6bd6328af9b13",
      "sha256:744315296a49be711c312dfa1b3a80516116f78c437367ff0bc678da1123e990",
      "sha256:379ef5d5cb402a5538413d7285b21aa58a560882d15f1f553f7868dc4b66afa8",
      "sha256:d00fd460effb7b066760f97447c071492d471c5176d05b8af1751806a1f905f8","sha256:4d0c196331523cfed7bf5bafd616ecb3855256838d850b6f3d5fba911f6c4123",
      "sha256:98b4a6242af2536383425ba2d6de033a510e049d9ca07ff501b95052da76e894"
    ]
  },
  "Metadata": {
    "LastTagTime": "0001-01-01T00:00:00Z"
  }
]

镜像分层的理解

所有的 Docker镜像都起始于一个基础镜像层,当进行修改或增加新的内容时,就会在当前镜像层之上,创建新的镜像层。
举一个简单的例子,假如基于 Ubuntu Linux 16.04 创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加 Python 包,就会在第一层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创建第三个镜像层,每个操作就是一层。该镜像当前已经包含 3 个镜像层,如下图所示(这只是一个用于演示的很简单的例子):

image.png

在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点非常重要。下图中举了一个简单的例子,每个镜像层包含 3 个文件,而镜像包含了来自两个镜像层的 6 个文件。
image.png

上图中的镜像层跟之前图中的略有区别,主要目的是便于展示文件。
下图中展示了一个稍微复杂的三层镜像,在外部看来整个镜像只有 6 个文件,这是因为最上层中的文件7 是文件 5 的一个更新版本。

image.png

这种情况下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中。
Docker 通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统。
Linux 上可用的存储引擎有 AUFS、Overlay2、Device Mapper、Btrfs 以及 ZFS。顾名思义,每种存储引擎都基于 Linux 中对应的文件系统或者块设备技术,并且每种存储引擎都有其独有的性能特点。
Docker 在 Windows 上仅支持 windowsfilter 一种存储引擎,该引擎基于 NTFS 文件系统之上实现了分层和 CoW。

下图展示了与系统显示相同的三层镜像。所有镜像层堆叠并合并,对外提供统一的视图。
image.png

特点
Docker镜像都是只读的,当容器启动时,一个新的可写层被加载到镜像的顶部。
这一层就是我们通常说的容器层,容器之下的都叫镜像层。
image.png


image.png

创建镜像的2种方式

  1. 使用commit命令。
  2. 使用Dockerfile文件来构建镜像。下下面会有讲

方式1:docker commit 从容器创建一个新的镜像
使用容器创建一个镜像,并提交到镜像仓库。其他人就可以下载使用了。
测试

# docker commit 命令:提交容器副本使之成为一个新的镜像(相当于备份容器为一个镜像文件)

# docker commit 命令格式: `docker commit -m="提交的描述信息" -a="作者" 容器id 要创建的目标镜像名:[TAG]`
# 语法和git的commit类似。TAG填:标签/版本

# 1. 从 DockerHub 下载tomcat镜像到本地并运行 -it 交互终端 -p 端口映射
docker run -it -p 8080:8080 tomcat

# 注意: 坑爹的docker,启动官方tomcat镜像,访问tomcat时发现 404 ,我们下载的tomcat镜像运行的容器中的 tomcat 目录中的 webapps 下没有root等文件
# 下载tomcat官方镜像,就是这个镜像(阿里云里的tomcat的webapps下没有任何文件)
# 进入tomcat查看cd到webapps下发现全部空的,反而有个webapps.dist里有对应文件,将该目录改名为webapps或复制为webapps即可解决

root@aba865b53114:/usr/local/tomcat# cp -r webapps.dist/* webapps

# 2. 删除上一步镜像产生的tomcat容器的文档
docker ps # 查看容器id
docker exec -it <容器id> /bin/bash
root@aba865b53114:/usr/local/tomcat# cd webapps
root@aba865b53114:/usr/local/tomcat/webapps# ls -l # 查看是否存在 docs文件夹
root@aba865b53114:/usr/local/tomcat/webapps# curl localhost:8080/docs/ # 可以看到 docs 返回的内容
root@aba865b53114:/usr/local/tomcat/webapps# rm -rf docs # 删除它
root@aba865b53114:/usr/local/tomcat/webapps# curl localhost:8080/docs/ # 再次访问返回 

# 3. 当前运行的tomcat实例就是一个没有docs的容器,我们使用这个容器做为模板commit一个没有docs的tomcat新镜像tomcat02
docker ps -l # 查看容器的id
# 注意: commit的时候,容器的名字不能有大写,否则报错: invalid reference format
docker commit -a="kuangshen" -m="add webapps app" 1e98a2f815b0 tomcat02:1.0
sha256:cdccd4674f93ad34bf73d9db577a20f027a6d03fd1944dc0e628ee4bf17ec 

[root@kuangshen /]# docker images #  查看,我们自己提交的镜像已经OK了。
REPOSITORY TAG IMAGE ID CREATED SIZE
tomcat02 1 .1 cdccd4674f93 About a minute ago 649MB
...

# 4. 这个时候,我们的镜像都是可以使用的,大家可以启动原来的tomcat,和我们新的tomcat02来测试看看。
[root@kuangshen ~]# docker run -it -p 8080:8080 tomcat02:1.0
# 如果你想要保存你当前容器的状态(比如安装了哪些软件、做了哪些配置等修改),可以通过commit,来提交容器为一个镜像,方便使用,类似于 VM 中的快照。

容器数据卷

什么是容器数据卷

docker的理念回顾
将应用和运行的环境打包形成容器运行,但是我们对于数据的要求,是希望能够持久化的在本地而不是在容器内的。就好比如果你在容器中安装一个MySQL,结果你把容器删了,数据也没了,这是不合理的。
所以我们希望容器之间可以共享数据,实现在Docker容器产生的数据可以同步到服务器本地(容器外的机器),为了能保存数据在Docker中我们就可以使用卷,让数据挂载到我们本地,这样数据就不会因为容器删除而丢失了。

容器数据卷作用
卷就是目录或者文件,存在一个或者多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过 Union File System , 提供一些用于持续存储或共享数据的特性。
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。

容器数据卷特点

  1. 数据卷可在容器之间共享或重用数据
  2. 卷中的更改可以直接生效
  3. 数据卷中的更改不会包含在镜像的更新中。也就是数据卷中数据的修改,使用改卷的容器来生成镜像时,不会记录在镜像文件中,和镜像文件无关。
  4. 数据卷的生命周期一直持续到没有容器使用它为止

总结:容器的持久化和同步操作,可以通过卷来实现,也就是说容器间可以通过卷来实现数据共享。

使用数据卷

方式一: 容器中直接使用命令来添加
挂载命令: docker run -it -v 主机目录:容器内目录
使用命令查看容器的详细信息,以此来查看数据卷是否挂载成功: docker inspect 容器id。看到"Mounts"这个节点,就是挂载的信息,"Mounts"内的"Source"就是挂载到的主机路径,"Destination"就是对应的容器内的路径。这两个路径内的数据是双向绑定的,也就是这两个路径内的数据永远都是相同的。

image.png

# 命令
docker run -it -v 宿主机绝对路径目录:容器内目录 镜像名

# 测试
[root@kuangshen ~]# docker run -it -v /home/ceshi:/home centos /bin/bash
# 以上就是把容器内的/home挂载到主机的/home/ceshi目录。即把容器/home目录映射到主机的/home/ceshi。

测试容器和宿主机之间数据共享: 可以发现,在容器中创建的文件和文件夹等数据都可以在宿主机的挂载的路径中看到。
image.png

测试容器停止退出后,主机修改数据是否会同步:

  1. 停止容器
  2. 在宿主机上挂载docker路径内修改文件,增加些内容,或新增文件
  3. 启动刚才停止的容器
  4. 然后查看容器内对应的目录内的文件,发现数据依旧同步。ok
    image.png

使用 docker 安装 mysql

# 1. 搜索镜像
[root@kuangshen ~]# docker search mysql
NAME DESCRIPTION STARS
mysql MySQL is a widely used, open-source relation...  9488

# 2. 拉取镜像
[root@kuangshen ~]# docker pull mysql:5.7
5.7: Pulling from library/mysql
54fec2fa59d0: Already exists
bcc6c6145912: Pull complete
...
Digest:
sha256:e821ca8cc7a44d354486f30c6a193ec6b70a4eed8c8362aeede4e9b8d74b8ebb
Status: Downloaded newer image for mysql:5.docker.io/library/mysql:5.7

# 3. 启动容器 参数-e:代表配置环境变量。第一次启动mysql应该配置一个初始密码
# 注意:  mysql的数据应该不丢失。先体验下 -v 挂载卷 参考官方文档
[root@kuangshen home]# 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:5.7

# 一条命令使用多个 -v 参数,一次挂载多个目录
# -d 后台运行
# -p 端口映射
# -v 卷挂载
# -e 环境配置
# --name 容器名称

# 4. 使用windows的sqlyog连接测试一下,windows的sqlyon连接到服务器的3310端口,服务器的3310端口和容器内的3306映射,这时候我们就可以连接上容器内的mysql了

# 5. 查看本地的 /home/mysql 目录
[root@kuangshen data]# pwd
/home/mysql/data
[root@kuangshen data]# ls
... test # 可以看到我们刚刚建立的mysql数据库在本地存储着

# 6. 删除mysql容器
[root@kuangshen data]# docker rm -f mysql01 # 删除容器,然后发现远程连接失败。
mysql[root@kuangshen data]# ls
... test # 可以看到我们刚刚建立的mysql数据库在本地存储着。也就是容器的删除,不影响容器外的主机的数据,实现了容器数据持久化的功能。

通过 Docker File 来添加镜像(了解)
DockerFile 是用来构建Docker镜像的构建文件,是由一些列命令和参数构成的脚本。
我们在这里,先体验下,后面我们会详细讲解 DockerFile。

# 1、我们在宿主机 /home 目录下新建一个 docker-test-volume文件夹 
[root@kuangshen home]# mkdir docker-test-volume # 说明: 在编写DockerFile文件中使用 VOLUME 指令来给镜像添加一个或多个数据卷,比如: `VOLUME["/dataVolumeContainer1","/dataVolumeContainer2","/dataVolumeContainer 3"]`
# 出于可移植和分享的考虑,我们之前使用的 -v 主机目录:容器目录 这种方式不能够直接在 DockerFile 中实现。 

# 2、编写DockerFile文件 
[root@kuangshen docker-test-volume]# pwd /home/docker-test-volume 
[root@kuangshen docker-test-volume]# vim dockerfile1 # 在该文件中写入以下4行命令,然后保存
[root@kuangshen docker-test-volume]# cat dockerfile1 
FROM centos 
VOLUME ["/dataVolumeContainer1","/dataVolumeContainer2"] 
CMD echo "-------end------"
CMD /bin/bash 

# 3、build后生成镜像,获得一个新镜像 kuangshen/centos
docker build -f /home/docker-test-volume/dockerfile1 -t kuangshen/centos . # 注意最后有个点

# 4、查看创建好的镜像。查出我们构建的镜像id是 0e97e1891a3d
[root@kuangshen docker-test-volume]# docker images
...

# 5、启动容器并进入容器
[root@kuangshen docker-test-volume]# docker run -it 0e97e1891a3d /bin/bash 
[root@f5824970eefc /]# ls -l 
total 56 
lrwxrwxrwx 1 root root 7 May 11 2019 bin -> usr/bin 
drwxr-xr-x 2 root root 4096 May 11 11:55 dataVolumeContainer1 # 数据卷目录 
drwxr-xr-x 2 root root 4096 May 11 11:55 dataVolumeContainer2 # 数据卷目录 
drwxr-xr-x 5 root root 360 May 11 11:55 dev 
drwxr-xr-x 1 root root 4096 May 11 11:55 etc 
drwxr-xr-x 2 root root 4096 May 11 2019 home
...

# 问题:通过上述步骤,容器内的卷目录地址就已经知道了,但是对应的主机目录地址在哪里呢?

# 5、我们在数据卷中新建一个文件
[root@f5824970eefc dataVolumeContainer1]# pwd 
/dataVolumeContainer1 
[root@f5824970eefc dataVolumeContainer1]# touch container.txt 
[root@f5824970eefc dataVolumeContainer1]# ls -l 
total 0 
-rw-r--r-- 1 root root 0 May 11 11:58 container.txt 

# 6、查看下这个容器的信息
[root@kuangshen ~]# docker inspect 0e97e1891a3d 
# 查看输出的Volumes
"Volumes": { 
  "/dataVolumeContainer1": {}, 
  "/dataVolumeContainer2": {} 
},

# 7、这个卷在服务器主机(宿主)对应的默认位置
/var/lib/docker/volumes/xxx

注意: 如果访问出现了 cannot open directory: Permission denied
解决办法: 在挂载目录后多加一个 --privileged=true参数即可

具名挂载和匿名挂载

匿名挂载: -v 容器内的路径,不用写主机路径,也是可以挂载的。docker run -d -P --name nginx01 -v /etc/nginx nginx: -P 是随机指定端口

docker volume --help: 查看docker volume(卷)这个命令的帮助文档

docker volume ls: 查看 docker 所有卷列表,VOLUME NAME 这一列如果是乱码,说明该卷是匿名挂载的。

具名挂载: 通过-v 卷名:容器内路径进行挂载,比如挂载卷名为 juming-nginx 的: docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx,只是给本地主机指定了一个自定义名字"juming-nginx",而没有指定具体的路径,使用命令 docker volume ls可以查看到刚刚指定挂载的"juming-nginx"的路径

# 查看挂载的路径 
[root@kuangshen ~]# docker volume inspect juming-nginx 
[ 
  {
  "CreatedAt": "2020-05-13T17:23:00+08:00", 
  "Driver": "local", 
  "Labels": null, 
  "Mountpoint": "/var/lib/docker/volumes/nginxconfig/_data", 
  "Name": "nginxconfig", 
  "Options": null, 
  "Scope": "local" 
  } 
]

注意:所有docker容器内的卷,在没有指定目录的情况下,都是挂载在/var/lib/docker/volumes/xxx

我们通过具名挂载,可以很方便的在服务器主机本地中找到我们挂载的卷,大多数情况我们都是使用具名挂载,几乎不会用匿名挂载。

如何区分具名挂载还是匿名挂载,还是指定路径挂载?:
如果以"/"开头的就是目录名,不是以"/"开头的就是卷名

  • -v 容器内路径: 匿名挂载
  • -v 卷名:容器内的路径: 具名挂载
  • -v /宿主主机路径:容器内路径: 指定路径挂载

扩展
限定容器挂载目录的读写权限。
ro readonly: 只读(容器对挂载卷只有只读权限,只能通过宿主机来修改该挂载卷)
rw readwrite: 可读可写(默认)

一旦对容器做了权限限定,容器对我们挂载出来的内容就会有权限限制。比如只能对挂载的内容读,而不能写。或者可以读也可以写。比如:

  • 只读: docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:ro nginx
  • 可读可写: docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx:rw nginx

数据卷容器

命名的容器挂载数据卷,其他容器通过挂载这个(父容器)实现数据共享,挂载数据卷的容器,称之为数据卷容器。
我们使用上一步的镜像: kuangshen/centos 为模板,运行容器 docker01,docker02,docker03,他们都会具有容器卷

"/dataVolumeContainer1"
"/dataVolumeContainer2"

我们来测试下,容器间传递共享

  1. 先启动一个容器docker01作为父容器,然后在容器docker01中的dataVolumeContainer2目录下新增文件 docker01.txt
    image.png
    退出不停止: ctrl+P+Q

  2. 创建docker02,docker03 让他们都继承docker01,发现他们都能看到上一步在docker01中创建的文件docker01.txt,也就是实现了数据共享。(继承的参数是--volumes-from)

# 创建容器 docker02 ,使用--volumes-from 来使它继承 docker01 ,这样 docker02 就可以访问 docker01 中创建的数据了,实现数据共享

# 继承容器 docker01 创建容器 docker02 ,发现 docker02 能访问到 docker01 的文件 docker01.txt
[root@kuangshen docker-test-volume]# docker run -it --name docker02 --volumes-from docker01 kuangshen/centos
[root@ea4c82779077 /]# cd /dataVolumeContainer2
[root@ea4c82779077 dataVolumeContainer2]# ls
docker01.txt

# 在 docker02 创建文件 docker02.txt
[root@95164598b306 dataVolumeContainer2]# touch docker02.txt
[root@95164598b306 dataVolumeContainer2]# ls
docker01.txt docker02.txt

# 继承容器 docker01 创建容器 docker03 ,发现 docker03 能访问到 docker01 创建的文件 docker01.txt 和 docker02 创建的文件 docker02.txt
[root@kuangshen docker-test-volume]# docker run -it --name docker03 --volumes-from docker01 kuangshen/centos
[root@ea4c82779077 /]# cd /dataVolumeContainer
[root@ea4c82779077 dataVolumeContainer2]# ls
docker01.txt docker02.txt

# 在 docker03 创建文件 docker03.txt
[root@95164598b306 dataVolumeContainer2]# touch docker03.txt
[root@95164598b306 dataVolumeContainer2]# ls
docker01.txt docker02.txt docker03.txt
  1. 回到容器docker01发现可以看到 docker02 和 docker03 创建的文件
[root@kuangshen docker-test-volume]# docker attach docker01
[root@799b6ea5db7c dataVolumeContainer2]# ls -l
total 0
-rw-r--r-- 1 root root 0 May 11 13 :20 docker01.txt
-rw-r--r-- 1 root root 0 May 11 13 :22 docker02.txt
-rw-r--r-- 1 root root 0 May 11 13 :24 docker03.txt
  1. 删除 docker01 ,进入 docker02 修改/新增文件后,发现docker03 还能访问 docker01 、 docker02 创建的文件,
[root@kuangshen docker-test-volume]# docker rm -f docker01
docker01

[root@kuangshen docker-test-volume]# docker attach docker02
[root@ea4c82779077 dataVolumeContainer2]# ls -l
total 0
-rw-r--r-- 1 root root 0 May 11 13 :20 docker01.txt
-rw-r--r-- 1 root root 0 May 11 13 :22 docker02.txt
-rw-r--r-- 1 root root 0 May 11 13 :24 docker03.txt

[root@ea4c82779077 dataVolumeContainer2]# touch docker02-update.txt
[root@ea4c82779077 dataVolumeContainer2]# ls -a
. .. docker01.txt docker02.txt docker02-update.txt docker03.txt
[root@ea4c82779077 dataVolumeContainer2]# Ctrl+P+Q 退出容器

[root@kuangshen docker-test-volume]# docker attach docker03
[root@799b6ea5db7c dataVolumeContainer2]# ls -l
total 0
-rw-r--r-- 1 root root 0 May 11 13:20 docker01.txt 
-rw-r--r-- 1 root root 0 May 11 13:22 docker02.txt 
-rw-r--r-- 1 root root 0 May 11 13:29 docker02-update.txt 
-rw-r--r-- 1 root root 0 May 11 13:24 docker03.txt
  1. 删除容器 docker02 ,发现 docker03 还能访问 docker02 创建的文件
[root@kuangshen docker-test-volume]# docker ps
CONTAINER ID IMAGE
95164598b306 kuangshen/centos
ea4c82779077 kuangshen/centos

[root@kuangshen docker-test-volume]# docker rm -f docker02
docker02

[root@kuangshen docker-test-volume]# docker attach docker03

[root@95164598b306 dataVolumeContainer2]# ls -l
total 0
-rw-r--r-- 1 root root 0 May 11 13 :20 docker01.txt
-rw-r--r-- 1 root root 0 May 11 13 :22 docker02.txt
-rw-r--r-- 1 root root 0 May 11 13 :29 docker02-update.txt
-rw-r--r-- 1 root root 0 May 11 13 :24 docker03.txt

# 在 docker03 创建文件 docker03-update.txt
[root@95164598b306 dataVolumeContainer2]# touch docker03-update.txt
  1. 新建容器 docker04 继承容器 docker03 ,然后再删除 docker03 ,发现 docker04 依然能访问 docker01、docker02、docker03 创建的文件
# 继承容器 docker03 新建容器 docker04 并进入 docker04 
[root@2119f4f23a92 /]# docker run -it -name docker04 --volumes-from docker03 kuangshen/centos 
[root@2119f4f23a92 /]# cd dataVolumeContainer2
[root@2119f4f23a92 dataVolumeContainer2]# ls -l
total -rw-r--r-- 1 root root 0 May 11 13 :20 docker01.txt
-rw-r--r-- 1 root root 0 May 11 13 :22 docker02.txt
-rw-r--r-- 1 root root 0 May 11 13 :29 docker02-update.txt
-rw-r--r-- 1 root root 0 May 11 13 :32 docker03-update.txt
-rw-r--r-- 1 root root 0 May 11 13 :24 docker03.txt

# 查看当前运行的容器
[root@kuangshen docker-test-volume]# docker ps
CONTAINER ID IMAGE NAMES
2119f4f23a92 kuangshen/centos docker95164598b306 kuangshen/centos docker

# 删除docker03
[root@kuangshen docker-test-volume]# docker rm -f docker03
docker03
[root@kuangshen docker-test-volume]# docker attach docker04
[root@2119f4f23a92 dataVolumeContainer2]# ls -l
total -rw-r--r-- 1 root root 0 May 11 13 :20 docker01.txt
-rw-r--r-- 1 root root 0 May 11 13 :22 docker02.txt
-rw-r--r-- 1 root root 0 May 11 13 :29 docker02-update.txt
-rw-r--r-- 1 root root 0 May 11 13 :32 docker03-update.txt
-rw-r--r-- 1 root root 0 May 11 13 :24 docker03.txt


得出结论: 容器之间配置信息的传递,数据卷的生命周期一直持续到没有容器使用它为止。存储在本机的文件则会一直保留。
/span>

DockerFile

大家想想官方制作的 Nginx,tomcat,mysql 这些镜像,我们自己怎么制作镜像?
我们要研究自己如何做一个镜像,而且我们写的微服务项目以及springboot打包上云部署,Docker就是最方便的。
微服务打包成镜像,任何装了Docker的地方,都可以下载使用,极其的方便。
流程: 开发应用=>DockerFile=>打包为镜像=>上传到仓库(私有仓库,公有仓库)=> 下载镜像 => 启动运行。

dockerfile: 就是用来构建Docker镜像的构建文件,是一个命令脚本文件,是由一系列命令和参数构成的脚本,镜像是一层一层的,dockerfile脚本文件也是一个一个命令,每个命令都是一层。

DockerFile Demo 的脚本内容如下:

# 创建一个dockerfile文件,文件名自定义,建议"Dockerfile"
# 文件中的内容格式: `指令(大写) 参数`
# 如下:

FROM centos
VOLUME ["valume01","valume02"]
CMD echo "---end---"
CMD /bin/bash

# 以上的每个(每行)命令就是镜像的一层

以上的过程可以用下图表示:
image.png
dockerfile是面向开发的,由开发人员编写该文件,我们以后发布项目,都是要编写dockerfile然后使用该文件制作镜像,最后交付给甲方或运维人员进行部署。Docker镜像逐渐成为企业交付的标准,必须要掌握。

构建步骤:

  1. 编写一个 DockerFile 文件
  2. docker build 构建成为一个镜像
  3. docker run 运行镜像
  4. docker push 发布镜像到 DockHub、阿里云镜像仓库

查看 DockerHub 看随意搜索的一个镜像,比如 CentOS ,点击 "Supported tags and respective Dockerfile links"下面的任意一个链接,都发现会跳转到github的一个 DockerFile 文件。可以通过研究这个文件,来了解一个镜像的构建过程。
地址: https://hub.docker.com/centos

image.png
image.png

如上,很多官方镜像都是基础包,很多功能和命令都没有,我们通常都会在官方的基础包上添加自己的功能比如添加jdk、tomcat、mysql、redis等,然后做成自己使用的镜像。
可以参考官方的 DockerFile 来写自己的 DockerFile ,然后自己构建镜像。

DockerFile 构建过程

DockerFile 基础知识

  1. 每条保留字指令都必须为大写字母且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. #表示注释
  4. 每条指令都会创建一个新的镜像层,并对镜像进行提交

流程

  1. docker从基础镜像运行一个容器
  2. 执行一条指令并对容器做出修改
  3. 执行类似 docker commit 的操作提交一个新的镜像层
  4. Docker 再基于刚提交的镜像运行一个新容器
  5. 执行 dockerfile 中的下一条指令直到所有指令都执行完成。

说明

  • DockerFile: 是docker镜像的构建文件,定义了构建镜像的一切操作步骤。DockerFile涉及的内容,包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当引用进行需要和系统服务和内核进程打交道,这时需要考虑如何设计 namespace的权限控制)等等。
  • DockerImages: 通过DockerFile构建生成的镜像,是用于最终发布和运行的产品,类似于jar包或war包。
  • Docker容器: 容器就是运行起来的镜像,提供服务的。

DockerFile 指令

DockerFile 文件中的关键字

FROM # 基础镜像,当前新镜像是基于哪个镜像的。比如 centos
MAINTAINER # 维护者的信息。一般写镜像维护者的姓名+email
RUN # 容器构建时需要运行的命令
ADD # 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包。比如 ADD 一个 tomcat 的压缩包时,会自动将该压缩包解压后的文件放入指定的目标目标,因此无需手动解压
WORKDIR # 指定镜像的工作目录。指定在创建容器后,终端默认登录进来的工作目录
VOLUME # 容器挂载到服务器主机的映射的路径。
EXPOSE # 当前容器对外暴露的端口。如果这里写了暴露端口,执行命令时就不用指定端口了。
CMD # 指定一个容器启动时要运行的命令,dockerFile中可以有多个CMD指令,但只有最后一个生效,且可被替代。可被替代的意思是: 比如执行写了CMD ls -a ,如果我们`docker run`,它会自动执行ls -a,如果我们执行`docker run -l`,-l 就会将 ls -a 覆盖,所以也不会执行 ls -a 命令
ENTRYPOINT # 指定一个容器启动时要运行的命令。和CMD一样,但 ENTRYPOINT 可以追加命令。追加命令的意思是: 比如执行写了 ENTRYPOINT ls -a ,如果我们`docker run`,它会自动执行ls -a,如果我们执行`docker run -l`,-l 就追加到 ls -a 后面,所以最后执行的是 ls -a -l
ONBUILD # 当构建一个被继承的DockerFile时,就会运行 ONBUILD 命令,父镜像在被子镜像继承后,父镜像的ONBUILD被触发
COPY # 类似ADD,拷贝文件和目录到镜像中
ENV # 构建镜像时设置环境变量

image.png
image.png

实战测试

Docker Hub 中99% 的镜像从scratch 这个基础镜像过来的,然后配置需要的软件和设置来进行构建。
image.png

创建一个自己的centos

自定义一个 centos 镜像

  1. 编写DockerFile
    先查看下官方默认的CentOS的信息:
    image.png

因为官方的镜像缺少一些功能,所以我们做的镜像得有如下功能才能更好: 登陆后的默认路径、vim编辑器、支持查看网络配置ifconfig。

准备编写DockerFlie文件,通过该文件构建镜像

# 编写dockerfile,文件名为: mydockerfile-centos,写入以下内容:
[root@kuangshen home]# cat mydockerfile-centos
FROM centos
MAINTAINER kuangshen<24736743@qq.com>
ENV MYPATH /usr/local # 设置进入容器后的默认目录
WORKDIR $MYPATH
RUN yum -y install vim
RUN yum -y install net-tools

EXPOSE 80
CMD eho $MYPATH
CMD eho "---end---"
CMD /bin/bash
  1. 通过 dockerfile 构建镜像
# 构建命令格式: `docker build -f <dockerfile路径> -t <新镜像名字>:<TAG> .`,命令最后有一个点,这个点表示当前目录

[root@kuangshen home]# docker build -f mydockerfile-centos - t mycentos:0.1 . # 注意最后有一个点

Sending build context to Docker daemon 6.144kB 
Step 1/10 : FROM centos 
---> 470671670cac 
Step 2/10 : MAINTAINER kuangshen<24736743@qq.com> 
---> Running in ac052943c151 Removing intermediate container ac052943c151 
---> 9d37c7174860 
Step 3/10 : ENV MYPATH /usr/local 
---> Running in a9d43e0b41bb 
Removing intermediate container a9d43e0b41bb 
---> 7a89b945c3a6 
Step 4/10 : WORKDIR $MYPATH ---> Running in b41f085b06bc

...

Verifying : net-tools-2.0-0.51.201
Step 9 /10 : CMD echo "---end---"
---> Running in d80e54fbc8ae
Removing intermediate container d80e54fbc8ae
---> c5b5391196cStep 10 /10 : CMD /bin/bash
---> Running in 5564a62d36da
Removing intermediate container 5564a62d36da
---> 18888023317c
Successfully built 18888023317c
Successfully tagged mycentos:0.
  1. 运行
    执行docker run -it <新镜像名字>:<TAG>,比如docker run -it mycentos:0.1
    image.png
    可以看到,默认的目录就是我们写在 dockerfile 中设置的目录,且我们自己的新镜像已经支持 vim、ifconfig的命令,对官方的centos镜像扩展并做成自己的镜像成功。

  2. 列出镜像的变更历史
    执行docker history 镜像id/镜像名
    image.png

通过docker history 镜像id/镜像名命令就可以查看镜像是怎么个操作的过程。
平时拿到一个镜像,可以通过此方式来看该镜像是怎么制作的了。

CMD 和 ENTRYPOINT 的区别
我们之前说过,这两个命令都是指定一个容器启动时要运行的命令

  • CMD: Dockerfile 中可以有多个CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换(CMD 的命令不会被执行,而是会被覆盖)
  • ENTRYPOINT: docker run 之后的参数会被当做参数传递给 ENTRYPOINT,然后追加成新的命令执行。

测试:
dockerfile中的CMD命令

# 1. 构建dockerfile
[root@kuangshen home]# vim dockerfile-cmd-test

# 创建一个 dockerfile,内容和名称如下
[root@kuangshen home]# cat dockerfile-cmd-test
FROM centos
CMD [ "ls", "-a" ]

# 2. build 镜像
[root@kuangshen home]# docker build -f dockerfile-cmd-test -t cmdtest. # cmdtest是创建的自定义的镜像名称

Sending build context to Docker daemon 22 .02kB
Step 1 /2 : FROM centos
---> 470671670cac
Step 2 /2 : CMD [ "ls", "-a" ]
---> Running in a3072987deRemoving intermediate container a3072987de---> 554bcSuccessfully built 554bcSuccessfully tagged cmdtest:latest
Successfully built dd8eljksadf # 这里是镜像id
...

# 3. 执行。发现上面 CMD [ "ls", "-a" ] 执行了
[root@kuangshen home]# docker run dd8eljksadf
...
bin
dev
etc
home
lib
lib
...

# 4. 如果我们希望用 -l ,即执行`ls -a -l` 列表展示信息,我们就需要加上 -l参数
[root@kuangshen home]# docker run cmdtest -l

docker: Error response from daemon: OCI runtime create failed:
container_linux.go:349: starting container process caused "exec: \"-l\":executable file not found in $PATH": unknown.

# 我们可以看到可执行文件找不到的报错,executable file not found。
# 因为跟在镜像名后面的是 command,运行时会替换 CMD 的默认值。所以这里的 -l 替换了原来的 CMD 的 "ls -a",而不是追加在原来的 "ls -a" 后面。而"-l" 根本不是命令,所以自然报错。
# 那么如果我们希望加入 -l 这参数,我们就必须重新完整的输入这个命令,覆盖 dockerfile 的 CMD:
[root@kuangshen home]# docker run cmdtest ls -al

dockerfile中的ENTRYPOINT命令

# 1. 构建dockerfile
[root@kuangshen home]# vim dockerfile-entrypoint-test

[root@kuangshen home]# cat dockerfile-entrypoint-test
FROM centos
ENTRYPOINT [ "ls", "-a" ]

# 2. build 镜像
[root@kuangshen home]# docker build -f dockerfile-entrypoint-test -t entrypointtest .
Sending build context to Docker daemon 23 .04kB
Step 1 /2 : FROM centos
---> 470671670cac
Step 2 /2 : ENTRYPOINT [ "ls", "-a" ]
---> Running in bac4aeRemoving intermediate container bac4ae---> ae07199f
Successfully built ae07199f
Successfully tagged entrypointtest:latest

# 3. 执行
[root@kuangshen home]# docker run ae07199f
.
..
.dockerenv
bin
dev
etc
home
lib
lib
...

# 4. 测试-l参数,发现可以直接使用,这里就是一种追加,我们可以明显的知道 CMD 和 ENTRYPOINT 的区别了
[root@kuangshen home]# docker run entrypointtest -l
total drwxr-xr-x 1 root root 4096 May 12 04 :21.
drwxr-xr-x 1 root root 4096 May 12 04 :21 ..
...

自定义镜像 tomcat

  1. 准备制作镜像需要用到的文件: tomcat、jdk 的压缩包
  2. 创建目录: mkdir -p kuangshen/build/tomcat
  3. (仅是建议)在上述目录下创建文件read.txt: touch read.txt
  4. 将 JDK(jdk-8u11-linux-x64.tar.gz) 和 tomcat(apache-tomcat-9.0.22.tag.gz) 压缩包拷贝进kuangshen/build/tomcat
  5. /kuangshen/build/tomcat 目录下新建一个文件,名为 "Dockerfile" (官方推荐的文件名,如果定义为 Dockerfile 那么build的时候就不用输入"-f Dockerfile文件名"了,docker会自动寻找该文件读取) 文件

Dockerfile 文件内容如下:

FROM centos
MAINTAINER kuangshen<24736743@qq.com>

#把宿主机当前的read.txt拷贝到容器/usr/local/路径下
COPY read.txt /usr/local/cincontainer.txt

#把java与tomcat添加到容器中。
# 注意,如果 ADD 命令参数的源文件是个归档文件(压缩文件),则docker会自动帮解压
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,使用以下3种方式任意一种即可

# 方式1
# ENTRYPOINT ["/usr/local/apache-tomcat-9.0.22/bin/startup.sh" ]

# 方式2
# CMD ["/usr/local/apache-tomcat-9.0.22/bin/catalina.sh","run"]

# 方式3
CMD /usr/local/apache-tomcat-9.0.22/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.22/bin/logs/catalina.out
# tail -F: 实时读取文件

当前文件状态
image.png

  1. 构建镜像
[root@kuangshen tomcat]# docker build -t diytomcat .
.....
Successfully built ffdf6529937d
Successfully tagged diytomcat:latest # 构建完成

# 构建完毕,查看镜像列表看看有没有成功
[root@kuangshen tomcat]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
diytomcat latest ffdf6529937d 20 seconds ago 636MB
  1. 运行启动 run
# 挂载了以后,只要我们把项目的代码发布到主机的 /home/kuangshen/build/tomcat/test即可自动发布到容器运行
docker run -d -p 9090:8080 --name mydiytomcat(容器名称) -v /home/kuangshen/build/tomcat/test:/usr/local/apache-tomcat-9.0.22/webapps/test -v /home/kuangshen/build/tomcat/tomcat9logs/:/usr/local/apache-tomcat-9.0.22/logs --privileged=true diytomcat(镜像名称)

# 如果不加 "--privileged=true" ,会报错: Docker访问出现cannot open directory . : Permission denied

image.png

  1. 验证测试访问 curl localhost:9090
    image.png

  2. 发布项目(由于做了卷挂载,可以直接把项目发布到服务器主机中就可以了)
    image.png

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>

index.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>
  1. 测试访问
    image.png

用浏览器访问 tomcat: curl localhost:9090/test/a.jsp

# 访问后查看tomcat日志
[root@kuangshen tomcat]# cd tomcat9logs/
[root@kuangshen tomcat9logs]# ll
total -rw-r----- 1 root root 6993 May 12 12 :50 catalina.2020-05-12.log
-rw-r----- 1 root root 7024 May 12 12 :53 catalina.out
-rw-r----- 1 root root 0 May 12 12 :47 host-manager.2020-05-12.log
-rw-r----- 1 root root 408 May 12 12 :47 localhost.2020-05-12.log
-rw-r----- 1 root root 150 May 12 12 :53 localhost_access_log.2020-05-12.txt
-rw-r----- 1 root root 0 May 12 12 :47 manager.2020-05-12.log
[root@kuangshen tomcat9logs]# cat catalina.out
....
-------my docker tomcat------- # 搞定

发布镜像

DockerHub

  1. 注册dockerhub https://hub.docker.com/signup
  2. 确定账号可以登录
  3. 在我们服务器上提交自己的镜像
# 1. 查看登录命令如何使用
[root@kuangshen tomcat]# docker login --help
Usage: docker login [OPTIONS] [SERVER]

# 2. 登录
[root@kuangshen tomcat]# docker login -u kuangshen
Password:
WARNING! Your password will be stored unencrypted in
/root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-
store
Login Succeeded

# 3. 将镜像发布出去
# 如下,要带上作者(自己的)名字,比如kuangshen/镜像名字:TAG(TAG一般是版本号)
[root@kuangshen tomcat]# docker push kuangshen/diytomcat:1.0
The push refers to repository [docker.io/library/diytomcat]
0f02399c6fdf: Preparing
0683de282177: Preparing
...

# 报错: 请求的资源访问被拒绝。原因往下看
denied: requested access to the resource is denied

# 原因: 本地镜像名无帐号信息,解决加 tag 即可。使用 tag 命令给镜像添加 tag
docker tag 251ca4419332 kuangshen/diytomcat:1.0

# 再次 push, ok。push 流程就是一层一层push的:
[root@kuangshen tomcat]# docker push kuangshen/diytomcat:1.0
The push refers to repository [docker.io/kuangshen/diytomcat]
0f02399c6fdf: Pushing [========> ]
9 .729MB/59.76MB
e79ea0c3a34e: Pushing [==========> ]
3 .188MB/15.41MB
...

阿里云镜像服务

  1. 登录阿里云
  2. 找到容器镜像服务
    image.png
  3. 创建命名空间,这里创建的命名空间叫: bilibili-kuangshen
    命名空间指的就是一个非常大的项目,该项目里可能有很多很多个镜像,可以把镜像放到命名空间里。
    注意: 一个阿里云账号只能创建3个命名空间。创建命名空间后,创建一个镜像仓库,创建镜像仓库时的代码源选择"本地仓库"即可
    image.png
  4. 创建镜像仓库,这里创建的镜像仓库叫: kuangshen-test
    image.png
    image.png
  5. 点击进入这个镜像仓库,可以看到所有的信息。可以通过公网地址访问创建的镜像仓库。
    image.png
  6. 测试推送发布
# 0. (可能需要先执行) docker logout

# 1. 登录阿里云
[root@kuangshen tomcat]# docker login --username=18xxxxx registry.cn-beijing.aliyuncs.com
Password:
WARNING! Your password will be stored unencrypted in
/root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-
store
Login Succeeded

# 2. 设置 tag
docker tag <镜像id> registry.cn-beijing.aliyuncs.com/<命名空间>/<镜像仓库名>:<镜像版本号>
[root@kuangshen tomcat]# docker tag 251ca4419332 registry.cn-beijing.aliyuncs.com/bilibili-kuangshen/kuangshen-test:v1.0

# 3. 推送命令
docker push registry.cn-beijing.aliyuncs.com/<命名空间>/<镜像仓库名>:<镜像版本号>
[root@kuangshen tomcat]# docker push registry.cn-beijing.aliyuncs.com/bilibili-kuangshen/kuangshen-test:v1.0
  1. 在阿里云镜像仓库查看效果。
    image.png

Dockerfile buil 总结

使用 Dockerfile build 镜像,然后可以将镜像 push 到镜像仓库,或者从镜像仓库pull 到本地,镜像run起来以后就是容器, commit 就会生成一个新镜像,类似于 git commit ,也相当于备份了。镜像也可以打包成一个 tar 包然后发给他人使用,这里的打包可以当做一种备份手段。

image.png

Docker 网络(铺垫 容器编排 集群部署)

理解Docker/Docker0网络详解

docker里面的网络核心就是docker0
准备工作: 清空所有的容器,清空所有的镜像

# 学习的时候可以先清空所有镜像和容器,方便理解不会混乱。
docker rm -f $(docker ps -a -q) # 删除所有容器
docker rmi -f $(docker images -qa) # 删除全部镜像

Docker的网络也是十分重要的一个点,希望大家可以认真理解。

我们先来做个测试,查看服务器主机的本地ip: 执行ip addr
image.png
这里我们分析可得,有三个网卡,这三个网络(网卡)代表三种不同的网络环境:

lo 127.0.0.1 # lo 是 127.0.0.1 也就是本机回环地址 
eth0 172.17.90.138 # 阿里云的私有IP 
docker0 172.18.0.1 # docker0 是 docker 帮我们生成的一个网卡。注意:  这里的docker0的ip地址是 172.18.0.1,记住它,下面将会用到该ip

问题: Docker 是如何处理容器网络访问的?

我们之前安装ES的时候,留过一个问题,就是安装Kibana的问题,Kibana得指定ES的ip地址。或者我们实际场景中,我们开发了很多微服务项目,那些微服务项目都要连接数据库,需要指定数据库的url地址,通过ip连接数据库。但是我们用Docker管理的话,假设数据库出问题了,我们重新启动运行一个,这个时候数据库的地址就会发生变化,docker会给每个容器都分配一个ip,且容器和容器之间是可以互相访问的。

我们可以测试下容器之间能不能ping通过:

# 启动tomcat01
[root@kuangshen ~]# docker run -d -P --name tomcat01 tomcat

# 查看容器内部的网络地址: 查看tomcat01的ip地址,docker会给每个容器都分配一个ip
[root@kuangshen ~]# docker exec -it tomcat01 ip addr # 直接给容器内发送命令执行,后面的`ip addr`就是要执行的命令。完后会发现有2个ip地址,一个是lo,另一个是类似于明伟eth0@if123的网卡,eth0@if123就是docker给容器分配的网络,它下方的172.18.0.2就是它的ip地址
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 
122: eth0@if123: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
  link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 
  inet 172.18.0.2/16 brd 172.18.255.255 scope global eth0 
    valid_lft forever preferred_lft forever

# 注意 122: eth0@if123,122和123这两个地方的数字总是连着号的

# 测试我们的linux服务器主机是否可以ping通tomcat容器:
[root@kuangshen ~]# ping 172.18.0.2 
PING 172 .18.0.2 (172.18.0.2) 56 (84) bytes of data.
64 bytes from 172 .18.0.2: icmp_seq= 1 ttl= 64 time= 0 .070 ms

# 说明容器外部的Linux主机是可以ping通容器内部的。
# 学过网络原理的都知道,192.168.x.x是同一个网段,所以192.168.n.m可以和任意的192.168.i.j能ping通。同理上面的docker0是172.18.0.1,当然也能ping通172.18.0.2

原理

  1. 每一个安装了Docker的linux主机都有一个docker0的虚拟网卡。这是个桥接网卡,该网卡是用的桥接模式,桥接到外网上,使用的是veth-pair技术
# 此时再次查看服务器主机的 ip addr,发现在我们容器启动后,又多了一个网卡
[root@kuangshen ~]# ip addr
1 : lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group
  default qlen link/loopback 00 :00:00:00:00:00 brd 00 :00:00:00:00:inet 127 .0.0.1/8 scope host lo
  valid_lft forever preferred_lft forever
2 : eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state
  UP group default qlen link/ether 00 :16:3e:30:27:f4 brd ff:ff:ff:ff:ff:ff
  inet 172 .17.90.138/20 brd 172 .17.95.255 scope global dynamic ethvalid_lft 310954997sec preferred_lft 310954997sec
3 : docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state
  UP group default
  link/ether 02 :42:bb:71:07:06 brd ff:ff:ff:ff:ff:ff
  inet 172 .18.0.1/16 brd 172 .18.255.255 scope global dockervalid_lft forever preferred_lft forever
123 : vethc8584ea@if122: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc
  noqueue master docker0 state UP group default
  link/ether 0a:4b:bb:40:78:a7 brd ff:ff:ff:ff:ff:ff link-netnsid 
# 发现: 本来我们有三个网络,我们在启动了个tomcat容器之后,多了一个 123 : vethc8584ea@if122的网络


注意:
容器内部的网卡是 "122: eth0@if123"
容器外部的主机新增的网卡是 "123 : vethc8584ea@if122"
注意其中的数字规律,容器内的数字和容器宿主的数字的前后是反着的

  1. 如果再启动一个容器,再查询网卡,发现每启动一个容器,linux主机就会新增一个虚拟网卡,同时多出来的网卡和容器内的网卡是成对的,如果删除这个容器,该网卡也会没了。比如主机多出来的网卡是"123:eth0@if122",容器内的网卡数字和主机是反着的"122:eth0@if123"
# 我们启动了一个tomcat01,主机的ip地址多了一个 123: vethc8584ea@if122 
# 然后我们在tomcat01容器中查看容器的ip是 122: eth0@if123 

# 我们再启动一个tomcat02观察
[root@kuangshen ~]# docker run -d -P --name tomcat02 tomcat 

# 然后发现linux主机上又多了一个网卡 "125: veth021eeea@if124"
# 我们看下tomcat02的容器内ip地址是 "124: eth0@if125"
[root@kuangshen ~]# docker exec -it tomcat02 ip addr 

# 观察现象:  
# tomcat01:
# linux主机ip是"123: vethc8584ea@if122"
# 容器内ip是"122: eth0@if123" 

# tomcat02:
# linux主机ip是"125: vethc8584ea@if124"
# 容器内ip是"124: eth0@if125" 

# 观察发现只要启动一个容器,就有一对网卡 
# veth-pair 就是一对的虚拟设备接口,它都是成对出现的。一端连着协议栈(通过主机连着外网?),一端彼此相连着。 正因为有这个特性,我们经常用veth-pair来充当着一个通讯的桥梁(所以Linux主机可以ping得通docker里面的容器),连接着各种虚拟网络设备! 
# “Bridge、OVS 之间的连接”,“Docker 容器之间的连接” 等等,以此构建出非常复杂的虚拟网络 结构,比如 OpenStack、Docker容器之间的连接、OVS的连接、Neutron,都是使用 veth-pair 技术实现的。
  1. 我们来测试下tomcat01和tomcat02容器间是否可以互相ping通
# 使用 tomcat02 来 ping tomcat01 的 ip :
[root@kuangshen ~]# docker exec -it tomcat02 ping 172.18.0.2
PING 172 .18.0.2 (172.18.0.2) 56 (84) bytes of data.
64 bytes from 172 .18.0.2: icmp_seq= 1 ttl= 64 time= 0 .110 ms
# 结论: 容器和容器之间是可以互相 ping 通的,也就是容器间是可以进行网络访问的。
  1. 我们来画一个网络模型图
    image.png
    结论: tomcat1和tomcat2共用的一个路由器,就是docker0,相当于 docker0 是用于连接所有容器的一个路由器(数据中转站),所有容器不指定网络的情况下,都是 docker0 路由的,docker默认会给容器分配一个可用ip。

网络原理基础知识扩展:

  • 255.255.0.1/16: 这里的斜杠后面的16指的是网段是16位
  • 二进制ip地址:00000000.00000000.00000000.00000000
  • 因为网段是16位,那么就截止出来二进制最前面的16位,所以00000000.00000000.xxxxxxxx.xxxxxxxx即十进制的ip255.255.x.x是一个网段,所以该网段中可用的地址是后面的两位没有确定的数的乘积,因为是8位的2进制,所以取值是255,所以255.255.x.x这个网段的可用地址有255*255=65535个,再减去它的回环地址0.0.0.0和最终地址0.0.0.255,可用的ip地址就是65533个地址
  • 同理,如果是255.255.0.1/24,代表该网段是前面的3位,只有最后一位是可支配的,也就是这个网段的ip可支配容量为255,减去回环地址和最终地址,只有253,斜杠后面的数字也叫域(比如局域网),数字越大,可支配的ip越少,数字越小,可支配的ip数量越多。

Docke 网络小结
Docker网络使用的是Linux桥接,在宿主机虚拟一个Docker容器网桥(docker0),Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址,称为Container-IP,同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的Container-IP直接通信。所以这个 docker0 大约可以给65535(实际是56633或少一点)个容器分配ip地址。
注意: Docker中所有的网络接口都是虚拟的(虚拟的转发效率高,因为都是在容器内部,相当于数据是在内网传递),类似于在主机中开了一个局域网,docker0网卡就是这个局域网的路由器,所有的容器都连接这个网卡。
image.png

Docker容器网络就很好的利用了Linux虚拟网络技术,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫veth pair);
Docker中的网络接口默认都是虚拟的接口。虚拟接口的优势就是转发效率极高(因为Linux是在内核中进行数据的复制来实现虚拟接口之间的数据转发,无需通过外部的网络设备交换),对于本地系统和容器系统来说,虚拟接口跟一个正常的以太网卡相比并没有区别,只是他的速度快很多。

思考一个场景,我们编写一个微服务,数据库连接地址原来是使用ip的,比如是"database: url=127.0.0.1:3306/xxxx",如果ip变化就不行了(比如ip变成了127.0.0.2),那我们能不能使用服务名访问呢?
jdbc:mysql://mysql:3306,这样的话哪怕mysql重启,我们也不需要修改配置了。类似于域名和服务的映射。
对于使用服务名连接的这种需求,docker提供了 --link 的操作

# 我们使用tomcat02,直接通过容器名ping tomcat01,发现ping不通
[root@kuangshen ~]# docker exec -it tomcat02 ping tomcat01
ping: tomcat01: Name or service not known

# 我们再启动一个tomcat03,但是启动时使用--link命令连接tomcat02
[root@kuangshen ~]# docker run -d -P --name tomcat03 --link tomcat02 tomcat
a3a4a17a2b707766ad4f2bb967ce1d94f658cd4cccef3bb8707395cdc71fa1e

# 这个时候,我们用tomcat03 ping tomcat02 就可以 ping 通了,就可以使用 服务名 ping 了,可以免去使用 ip 来 ping
[root@kuangshen ~]# docker exec -it tomcat03 ping tomcat02
PING tomcat02 (172.18.0.3) 56 (84) bytes of data.
64 bytes from tomcat02 (172.18.0.3): icmp_seq= 1 ttl= 64 time= 0 .093 ms
64 bytes from tomcat02 (172.18.0.3): icmp_seq= 2 ttl= 64 time= 0 .066 ms

# 再来测试,用 tomcat03 ping tomcat01 ,发现失败
[root@kuangshen ~]# docker exec -it tomcat03 ping tomcatping: tomcat01: Name or service not known

# 再来测试,tomcat02 是否可以ping tomcat03 反向也 ping 不通
[root@kuangshen ~]# docker exec -it tomcat02 ping tomcatping: tomcat03: Name or service not known

# 结论: 如果是使用 "tomcat03 --link tomcat02",那么 tomcat03 就可以使用容器名 ping 通(连接)  tomcat02 (`docker exec -it tomcat03 ping tomcat02`),但是反过来不能使用容器名 tomcat02  ping 通 tomcat03,除非 "tomcat02 --link tomcat03"

思考,这个原理是什么呢?我们进入tomcat03中查看下host配置文件

[root@kuangshen ~]# 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 .18.0.3 tomcat02 b80da266a3ad # 发现tomcat2直接被写在这里
172 .18.0.4 a3a4a17a2b

# 所以这里其实就是配置了一个 hosts 地址而已。
# 原因: --link的时候,直接把需要link的主机的域名和ip直接配置到了hosts文件中了。

--link参数早都过时了,不推荐使用,我们可以使用自定义网络的方式

自定义网络

探究 inspect 命令

  • 命令: docker network inspect <容器tomcat03的id>用于显示 容器tomcat03 的网络详细信息,如下:
    image.png
  • 命令docker inspect <容器id>,比如docker inspect <容器tomcat03的id>,查看 容器 tomcat03 的配置。

自定义网络不适用docker0。
docker0的问题: 不支持容器名连接访问。

查看网络命令的选项: docker network --help
image.png

[root@kuangshen ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
4eb2182ac4b2 bridge bridge local
ae2b6209c2ab host host local
c037f7ec7e57 none null local

所有网路模式

网络模式配置说明
bridge模式--net=bridge默认值,在Docker网桥docker0上为容器创建新的网络栈
none模式--net=none不配置网络,用户可以稍后进入容器,自行配置
container模式--net=container:name/id容器和另外一个容器共享Network namespace。kubernetes中的pod就是多个容器共享一个Network namespace。(此模式用得少,局限性很大)
host模式--net=host容器和宿主机共享Network namespace
用户自定义--net=自定义网络用户自己使用network相关命令定义网络,创建容器的时候可以指定为自己定义的网络

查看一个具体的网络的详细信息

# 命令
[root@kuangshen ~]# docker network inspect 4eb2182ac4b2
[
  {
  "Name": "bridge",
  "Id":
  "4eb2182ac4b23487e1eb6e06df56c71ab6f0adc7ccc0962b4747d0eed5ad6690",
  "Created": "2020-05-11T15:44:20.131441544+08:00",
  "Scope": "local",
  "Driver": "bridge",
  "EnableIPv6": false,
  "IPAM": {
    "Driver": "default", # 默认的
    "Options": null,
    "Config": [
      {
      // 默认docker0是管理这个子网范围内的。0到16位,也就是剩余的 255*255=65535 个 ip 可以分配,去
      掉 0 个 255 ,我们有 65534 可以分配的ip
      // docker0网络默认可以支持创建 6 万多个容器ip不重复
      "Subnet": "172.18.0.0/16",
      "Gateway": "172.18.0.1"
      }
    ]
  },
  "Internal": false,
  "Attachable": false,
  "Ingress": false,
  "ConfigFrom": {
    "Network": ""
  },
  "ConfigOnly": false,
  "Containers": { // 每个容器如果启动时不指定 ip ,docker就会随机给容器指定一个 ip
    "a3a4a17a2b707766ad4f2bb967ce1d94f658cd4cccef3bb8707395cdc71fa1e7": {
      "Name": "tomcat03",
      "EndpointID":
      "5fe62d0be3a1343aa74f6bb77885d3657b191210cafad200e5054e1bdfe56be9",
      "MacAddress": "02:42:ac:12:00:04",
      "IPv4Address": "172.18.0.4/16", # 启动容器时候 docker 随机分配的 ip
      "IPv6Address": ""
    },
    "b80da266a3ad85fcc874c5f0d3c897246ebc40533cb745bb24180237cc3167b1": {
      "Name": "tomcat02",
      "EndpointID":
      "6232cac0c4e7fed30c9fa5eebc9238f9fc44f548f257344b5440d6d486825256",
      "MacAddress": "02:42:ac:12:00:03",
      "IPv4Address": "172.18.0.3/16",
      "IPv6Address": ""
    },
    "f38239e2b405bccf8c93bd1052f336f661033b9b27ef9b3f99a018c108e06f87": {
      "Name": "tomcat01",
      "EndpointID":
      "31dc174b8f3f15f217acdc3ac7e8a235a03d59438f863706c0143b4426772c5e",
      "MacAddress": "02:42:ac:12:00:02",
      "IPv4Address": "172.18.0.2/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": {}
  }
]

自定义网卡

  1. 删除原来的所有容器(因为是学习,所以需要让学习环境干净一点,避免干扰)
[root@kuangshen ~]# docker rm -f $(docker ps -aq)

# 恢复到了最开始的样子
[root@kuangshen ~]# ip addr
1 : lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group
default qlen link/loopback 00 :00:00:00:00:00 brd 00 :00:00:00:00:inet 127 .0.0.1/8 scope host lo
  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 00:16:3e:30:27:f4 brd ff:ff:ff:ff:ff:ff 
  inet 172.17.90.138/20 brd 172.17.95.255 scope global dynamic eth0 
    valid_lft 310951436sec preferred_lft 310951436sec 
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default 
  link/ether 02:42:bb:71:07:06 brd ff:ff:ff:ff:ff:ff 
  inet 172.18.0.1/16 brd 172.18.255.255 scope global docker0 
    valid_lft forever preferred_lft forever  

  1. 接下来我们来创建容器,但是我们知道默认创建的容器都是docker0网卡的
# 如果我们启动容器时不配置网络,则使用默认的网络配置,也就是会自动在我们命令后面加上 `--net bridge` ,实际是完整执行:  
dockerdocker run -d -P --name tomcat01 --net bridge tomcat`
# 这个就是 docker0

# docker0网络的特点
# 1. docker0是默认的
# 2. 域名访问不通
# 3. --link 域名通了,但是删了又不行
  1. 我们可以让容器创建的时候使用自定义网络

image.png

# 创建一个自定义网络(网卡/虚拟网卡),使用"--driver bridge"将该网络设置为桥接模式,子网是 192.168.0.0/16,网关(即内网的数据从哪个ip出去)是 192.168.0.1 ,和家里的路由器网络配置一样的道理
[root@kuangshen ~]# docker network create --driver bridge --subnet
192.168.0.0/16 --gateway 192.168.0.1 mynet
09bd09d8d3a6b33e6d19f49643dab551e5a45818baf4d5328aa7320c6dcfc

# 执行完后,查看docker的网卡列表
[root@kuangshen ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE 
4eb2182ac4b2 bridge bridge local 
ae2b6209c2ab host host local 
09bd09d8d3a6 mynet bridge local 
c037f7ec7e57 none null local

# 查看docker指定的网卡配置,这里看我们刚刚创建的网卡 mynet
# docker network inspect <网卡名>
[root@kuangshen ~]# docker network inspect mynet
[
  {
  "Name": "mynet",
  "Id":
  "09bd09d8d3a6b33e6d19f49643dab551e5a45818baf4d5328aa7320c6dcfc236",
  "Created": "2020-05-13T13:29:33.568644836+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": {},
  "Options": {},
  "Labels": {}
  }
]

# 使用自己创建的网卡 mynet 来启动容器
# docker run -d -P --name tomcat-net-01(说明:自定义容器名) --net mynet(说明:网卡名) tomcat(说明:镜像名)
[root@kuangshen ~]# docker run -d -P --name tomcat-net-01 --net mynet tomcat
065f82e947c760c63539ab4c0de0d683787ec7ac6d0dcaa71f64e191319f9fe

[root@kuangshen ~]# docker run -d -P --name tomcat-net-02 --net mynet tomcat
2e85d71afe87c87166786b0bbae2d90eefb969d716fcd78a21173add5956cb

[root@kuangshen ~]# docker ps
CONTAINER ID IMAGE PORTS NAMES
2e85d71afe87 tomcat 0 .0.0.0:32772->8080/tcp tomcat-net-02
065f82e947c7 tomcat 0 .0.0.0:32771->8080/tcp tomcat-net-01

# 再来查看下
[root@kuangshen ~]# docker network inspect mynet
[
  {
  "Name": "mynet",
  "Id":
  "09bd09d8d3a6b33e6d19f49643dab551e5a45818baf4d5328aa7320c6dcfc236",
  ............
  "Containers": { # 查看使用该网卡的容器的网络信息,比如ip等
    "065f82e947c760c63539ab4c0de0d683787ec7ac6d0dcaa71f64e191319f9fe7": {
      "Name": "tomcat-net-01",
      "EndpointID":
      "d61cef1bc294d7f10fb6d9b728735fc87bed79e4e02f5298374f0fab3e9b2da6",
      "MacAddress": "02:42:c0:a8:00:02",
      "IPv4Address": "192.168.0.2/16",
      "IPv6Address": ""
    },
    "2e85d71afe87c87166786b0bbae2d90eefb969d716fcd78a21173add5956cb12": {
      "Name": "tomcat-net-02",
      "EndpointID":
      "adbc37a20526c2985c3589382998a3d106ef722662c7b296a57d8a7c8f449f38",
      "MacAddress": "02:42:c0:a8:00:03",
      "IPv4Address": "192.168.0.3/16",
      "IPv6Address": ""
    }
  },
  "Options": {},
  "Labels": {}
  }
]

# 我们来测试ping容器名和ip试试,发现都可以ping通
# 使用容器 "tomcat-net-01" ping "tomcat-net-02"的ip
[root@kuangshen ~]# docker exec -it tomcat-net-01 ping 192.168.0.3
PING 192 .168.0.3 (192.168.0.3) 56 (84) bytes of data.
64 bytes from 192 .168.0.3: icmp_seq= 1 ttl= 64 time= 0 .093 ms

# 使用容器 "tomcat-net-01" ping "tomcat-net-02"
[root@kuangshen ~]# docker exec -it tomcat-net-01 ping tomcat-net-02
PING tomcat-net-02 (192.168.0.3) 56 (84) bytes of data.
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq= 1 ttl= time= 0 .063 ms
64 bytes from tomcat-net-02.mynet (192.168.0.3): icmp_seq= 2 ttl= time= 0 .066 ms

# 发现,我们自定义的网络docker都已经帮我们维护好了对应的关系,所以我们平时都可以这样使用网络,不使用--link效果一样,所有东西实时维护好,直接域名 ping 通。

使用自定义网络可以实现不同的集群使用不同的网络,比如 redis 集群使用同一个网络(网卡),也就相当于所有的 redis 服务都运行在同一个区域网中,所有的 mysql 服务都运行在同一个区域网中,保证了数据安全和干净。

我们使用自定义网络的好处就是网络隔离:
大家公司项目部署的业务都非常多,假设我们有一个商城,我们会有订单业务(操作不同数据),会有订单业务购物车业务(操作不同缓存)。如果在一个网络下,有的程序猿的恶意代码就不能防止了,所以我们就在部署的时候网络隔离,创建两个桥接网卡,比如订单业务(里面的数据库,redis,mq,全部业务 都在order-net网络下)其他业务在其他网络。

网络连通

image.png
docker0(默认的网卡,启动容器时候如果不指定网卡,则使用docker0作为网卡)和自定义网络(网卡)肯定不通,因为网关不一样,如上图的 tomcat1 和 tomcat-net-01 肯定是 ping 不通的。但是我们可以将容器 tomcat1 连接到 mynet 这个网络,就可以从连通 tomcat1 和 tomcat-net-01 ,使得他们互相 ping 成功了。
可以把上图的连线当成网线,docker0和mynet当成是路由器(网卡/或网关不一样的路由设备),tomcat1、tomcat2 和 tomcat-net-01、tomcat-net-02分别当成是docker0和mynet子网络(局域网)中的设备,tomcat1、tomcat2的网关都是172.17.0.1,所以可以互相ping得通,而tomcat1和tomcat-net-01不是同一个网关所以ping不通。

# 启动 tomcat01 和 tomcat02,该容器运行在在docker0网络下
[root@kuangshen ~]# docker run -d -P --name tomcat01 tomcat
bcd122e0dcf6bf8c861eaa934911f98a5497a4954f3fde9575e496160bd

[root@kuangshen ~]# docker run -d -P --name tomcat02 tomcat
6183aaeca003a3e5a3549a37f9c1040551320ae358807b4aaad547a986afb


# 查看当前的容器
[root@kuangshen ~]# docker ps
CONTAINER ID IMAGE PORTS NAMES
6183aaeca003 tomcat 0.0.0.0:32774->8080/tcp tomcat
bcd122e0dcf6 tomcat 0.0.0.0:32773->8080/tcp tomcat
2e85d71afe87 tomcat 0.0.0.0:32772->8080/tcp tomcat-net-01
65f82e947c7 tomcat 0.0.0.0:32771->8080/tcp tomcat-net-02

# 我们来查看下network帮助,发现一个命令 connect
[root@kuangshen ~]# docker network --help
Commands: 
connect Connect a container to a network # 连接一个容器到一个网络
create Create a network
disconnect Disconnect a container from a network
inspect Display detailed information on one or more networks
ls List networks
prune Remove all unused networks
rm Remove one or more networks


# 连通容器与网卡的命令: docker network connect [OPTIONS] NETWORK CONTAINER
# 我们来测试一下
# 将容器tomcat01 连通到 mynet-docker
[root@kuangshen ~]# docker network connect mynet tomcat01
[root@kuangshen ~]# docker network inspect mynet
[
{
  ...
  "Containers": {
    "065f82e947c760c63539ab4c0de0d683787ec7ac6d0dcaa71f64e191319f9fe7": {
      "Name": "tomcat-net-01",
      "EndpointID":
      "d61cef1bc294d7f10fb6d9b728735fc87bed79e4e02f5298374f0fab3e9b2da6",
      "MacAddress": "02:42:c0:a8:00:02",
      "IPv4Address": "192.168.0.2/16",
      "IPv6Address": ""
    },
    "2e85d71afe87c87166786b0bbae2d90eefb969d716fcd78a21173add5956cb12": {
      "Name": "tomcat-net-02",
      "EndpointID":
      "adbc37a20526c2985c3589382998a3d106ef722662c7b296a57d8a7c8f449f38",
      "MacAddress": "02:42:c0:a8:00:03",
      "IPv4Address": "192.168.0.3/16",
      "IPv6Address": ""
    },
    "bcd122e0dcf6bf8c861eaa934911f98a5497a4954f3fde9575e496160bd23287": {  // 在这里发现我们的 tomcat01 就被配置进这里了,官方称这种配置为一个容器两个 ip 地址。同理阿里云服务器也是一个公网ip,一个私网ip
      "Name": "tomcat01",
      "EndpointID":
      "b2bf2342948e17048d872a4d5603c77e90d0e032439d510e86c10a1acc3928d9",
      "MacAddress": "02:42:c0:a8:00:04",
      "IPv4Address": "192.168.0.4/16",
      "IPv6Address": ""
    }
  },
  ...
}
]

# tomcat01 可以 ping 通 tomcat-net-01 了
[root@kuangshen ~]# docker exec -it tomcat01 ping tomcat-net-01
PING tomcat-net-01 (192.168.0.2) 56 (84) bytes of data.
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq= 1 ttl= time= 0 .071 ms
64 bytes from tomcat-net-01.mynet (192.168.0.2): icmp_seq= 2 ttl= time= 0 .067 ms

# tomcat02 依旧 ping 不通 tomcat-net-01 。因为该容器没有网络连通到 tomcat-net-01 所在的网卡
[root@kuangshen ~]# docker exec -it tomcat02 ping tomcat-net-01
ping: tomcat-net-01: Name or service not known

结论: 一个容器(内的程序)要跨网络(跨网卡/跨网关)操作其他容器,就需要使用 docker network connect [OPTIONS] NETWORK CONTAINER 来将一个容器连接到另一个网卡(网络)上,使得该容器有2个网卡,就可以ping得通了。

实战: 部署一个Redis集群

image.png
如图,我们需要启动6个服务(容器),其中3个是对外服务,3个是对外的3个服务的备份,若有其中一个比如2000-3000这个分片的服务崩了,就需要把它的备份服务来顶上,使得整个系统可以正常运行。将这6个容器都运行在同一个自定义的网卡中,比如自定义叫redis的网卡。
高可用: 出现故障率就会有解决方案,比如一个服务出现故障,会有另一个服务马上顶上,使得软件系统能正常使用。
分片:比如id为1-100,可以分成5个服务,比如服务A负责id为0-19的用户、服务B负责id为20-39的用户...

# 创建名为redis的一个网卡,子网是 172.38.0.0/16
docker network create redis --subnet 172.38.0.0/16

# 查看网卡列表
docker network ls

# 查看网卡配置
docker network inspect 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 bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout cluster-announce-ip 172 .38.0.1${port}
cluster-announce-port cluster-announce-bus-port appendonly yes
EOF
done
# cluster-enabled yes 意思是开启集群配置


# 启动: 方法1:使用脚本启动6个容器
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; \

# 启动: 方法2:手动启动 6 个容器
# 启动第1个容器
docker run -p 6371:6379 -p 16371:16379 --name redis-1 \
-v /mydata/redis/node-1/data:/data \
-v /mydata/redis/node-1/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172 .38.0.11 redis:5.0.9-alpine3.11 redis-server
/etc/redis/redis.conf

# 启动第2个容器
docker run -p 6376 :6379 -p 16376 :16379 --name redis-6 \
-v /mydata/redis/node-6/data:/data \
-v /mydata/redis/node-6/conf/redis.conf:/etc/redis/redis.conf \
-d --net redis --ip 172 .38.0.16 redis:5.0.9-alpine3.11 redis-server
/etc/redis/redis.conf

# 启动第3个容器 ...
# 启动第4个容器 ...
# 启动第5个容器 ...
# 启动第6个容器 ...

# 进入一个redis,注意这里是 sh命令
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

# 连接集群
redis-cli -c

# 查看集群信息
cluster info

# 查看节点
cluster nodes

set a b
# 发现键值对存到了xxx容器,此时如果我们将该容器停止, 然后再次get a,发现依旧可以获取值(因为它的备份节点(从机)给顶上了),实现了高可用

IDEA整合Docker

创建项目

  1. 使用 IDEA 构建一个 SpringBoot 项目

  2. 编写一个helloController

@RestController
public class HelloController {
  @GetMapping("/hello")
  public String hello(){
    return "hello,kuangshen";
  }
}
  1. 启动测试下,端口修改下,避免 8080 冲突,然后本地访问第二步编写的接口,发现没问题。接着往下看

  2. 打jar包
    image.png
    有了 jar 包,我们就可以作镜像了。打包好了了 jar 包,先使用命令java -jar xxx.jar测试一下打包好的 jar 包是否可以正常使用。

打包镜像

  1. 在项目下编写 Dockerfile 文件,将打包好的 jar 包拷贝到 Dockerfile 同级目录
FROM java:8 # 项目是基于java8构建的

# 将主机本地的 jar 包复制到 容器中
COPY *.jar /app.jar

CMD ["--server.port=8080"] # 指定项目运行的端口,如果启动容器时没有设定`--server.port 端口`,则使用这里设置的端口号作为默认运行端口

# 指定容器要对外暴露的端口
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
  1. 将 Dockerfile 和 项目的 jar 包上传到 linux 服务器上,构建运行
[root@kuangshen idea]# pwd
/home/idea

[root@kuangshen idea]# ll
total -rw-r--r-- 1 root root 17634294 May 14 12:33 demo-0.0.1-SNAPSHOT.jar
-rw-r--r-- 1 root root 207 May 14 12:32 Dockerfile

# 构建镜像
docker build -t myspringboot-img .

# 查看镜像
docker images

# 使用镜像 myspringboot-img 启动容器 myspringboot
docker run -d -P --name myspringboot myspringboot-img
[root@kuangshen ~]# docker ps
CONTAINER ID IMAGE PORTS NAMES
2366c960ba99 myspringboot 0 .0.0.0:32779->8080/tcp myspringboot

# 测试访问接口
[root@kuangshen ~]# curl localhost:32779
[root@kuangshen ~]# curl localhost:32779/hello

IDEA安装插件

了解即可。以后CI/CD,就完全没必要这样做。

  1. IDEA安装插件
    image.png

  2. 配置docker连接集成
    image.png

  3. 集成了 docker 插件就可以在 IDEA 中操作 Docker 内部的容器和镜像了,但是很鸡肋这个功能,对于我们开发人员来说之后学习的 CI/CD 才是真正在企业中的王道。

docker基础到此结束,下面的内容都是docker进阶课程,进阶课程需要准备4台服务器。

Docker Compose

简介

学 docker 基础的时候,如果创建一个镜像,需要手动执行DockerFile build run且只能操作单个容器,非常麻烦。
但如果有100个微服务,都要一个个手动操作,就没法用,这100个微服务之间他们之间还有依赖关系,都要手动100个启停就累死了。
Docker Compose 来轻松高效的管理容器,可以让我们定义和运行多个容器,可以将100个服务定义在一个文件里,实现对多个服务一键启停。

官方介绍

# Compose 用于定义、运行多个容器。
# YAML fifile 配置文件。
# 使用一些简单的命令来创建和启动停止服务,那么这些命令有哪些?
Compose is a tool for defifining and running multi-container Docker applications. With Compose,
you use a YAML fifile to confifigure your application’s services. Then, with a single command, you
create and start all the services from your confifiguration. To learn more about all the features of
Compose, see the list of features.

# 所有的环境都可以使用 Compose
Compose works in all environments: production, staging, development, testing, as well as CI
workflflows. You can learn more about each case in Common Use Cases.

**使用 Compose 的三个步骤:**
Using Compose is basically a three-step process:
  1. Defifine your app’s environment with a Dockerfile so it can be reproduced anywhere
    + 将应用程序的运行环境定义在 Dockerfifile 中,保证我们的项目在任何地方可以运行。
  2. Defifine the services that make up your app in docker-compose.yml so they can be run together in an isolated environment.
    + services 什么是服务,怎么定义服务。
    + docker-compose.yml 这个文件怎么写。
  3. Run docker-compose up and Compose starts and runs your entire app.
    + 启动项目

Docker Compose 的作用:批量容器编排。
我自己理解
Compose 是Docker官方的开源项目,如果要使用得需要安装。
Dockerfile 让程序在任何地方运行。 web服务。 redis、mysql、nginx ... 多个容器。 run
Compose

# 如这里的配置,在启动 web 服务时,因为 web 服务的 links 绑定了 redis ,那么 docker 就会先去启动 redis,再去启动 web 服务。
version: '2.0'
services:
  web:
    build: . # 使用 build 命令构建镜像,"." 是说 DockerFile 的路径是和 docker-compose.yml 在同一个目录,也就是 docker-compose.yml 的当前目录
    # build: 
    #   dockerfile: MyDockerFile # 也可以手动指定 DockerFile 的文件名
    ports:
      - "5000:5000"
    volumes:
      - .:/code
      - logvolume01:/var/log
    links:
      - redis
  redis:
    image: redis
volumes:
  logvolume01: {}

这样,使用命令 docker-compose up 就可以实现多个容器/应用的启动和停止。
Compose:重要的概念。

  • 服务services: 就是容器/应用,比如 web 、 redis 、 mysql 容器/应用
  • 项目project: 就是一组关联的容器,比如一个项目可能包含了多个互相关联的容器。 最简单的就是一个博客项目有web、mysql这2个服务,需要同时启动。

安装

  1. 下载
# 下载方法1: 使用该方法下载慢。建议使用方法2下载
sudo curl -L "https://github.com/docker/compose/releases/download/1.26.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 下载方法2: 使用这个方式可能下载快
curl -L https://get.daocloud.io/docker/compose/releases/download/1.25.5/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose

image.png
2. 授权

sudo chmod +x /usr/local/bin/docker-compose

# 测试是否安装成功
cd /usr/local/bin
docker-compose version # 弹出版本号就是安装成功

image.png
多看官网.....

体验

地址:https://docs.docker.com/compose/gettingstarted/
体验的是 一个python 做的计数器,使用的是 redis 来计数,跟着连接的步骤来操作即可。

  1. 创建项目目录并进入该目录 mkdir composetest && cd composetest
  2. 编写应用代码,一个最简单的 Flask 应用个,写在 app.py ,及项目依赖 requirements.txt ,内容如下:
# app.py
import time
import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)
# 注意,这里的 host 写的是 redis ,也就是通过域名访问 redis 服务

def get_hit_count():
    retries = 5
    while True:
        try:
            return cache.incr('hits')
        except redis.exceptions.ConnectionError as exc:
            if retries == 0:
                raise exc
            retries -= 1
            time.sleep(0.5)

@app.route('/')
def hello():
    count = get_hit_count()
    return 'Hello World! I have been seen {} times.\n'.format(count)
flask
redis
  1. 编写 Dockerfifile 将应用打包为镜像。 Dockerfile 内容如下
# syntax=docker/dockerfile:1
FROM python:3.7-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . . # 把当前文件内的文件全都拷贝到容器中去
CMD ["python", "app.py"]
  1. Docker-compose yaml文件 (定义整个服务,及运行应用所需要的环境。 web、redis) ,实现完整的上线多个互相关联的服务。docker-compose.yml 内容如下:
version: "3.9"
services:
  web: # 自己构建镜像
    build: . # 使用 build 命令构建镜像,"." 是说 DockerFile 的路径是和 docker-compose.yml 在同一个目录,也就是 docker-compose.yml 的当前目录
    ports:
      - "8000:5000"
  redis: # 从官方 pull 现成的 redis 镜像来使用
    image: "redis:alpine"
  1. 启动 compose 项目(在 docker-compose.yml 所在的目录内执行命令 docker-compose up 即可完整上线整个项目的所有服务)
  2. 测试,执行 curl http://localhost:8000/ 或浏览器直接访问网址即可。每访问/刷新一次也没,次数就会+1。可以通过

体验项目的流程

  1. 创建网络(网卡?)
  2. 根据docker-compose.yml文件的配置执行
  3. 启动服务。

docker-compose.yml文件的配置执行时启动会显示下面信息:

Creating composetest_web_1 ... done
Creating composetest_redis_1 ... done

如果是正常启动,会如下图,有2个服务被启动起来,也可以使用 docker ps 来查看:
image.png

  1. 文件夹名(项目目录名): composetest
  2. 服务名: web、redis

docker 会自动根据项目名和服务名创建服务,比如 composetest_web_1 、 composetest_redis_1 等

如果执行过程中有提示"Warning: Image for serivce web was build beacause it did not already exist. To rebuild this image you must use docker-compose build or docker-compose up --build.",那么就手动执行一下docker-compose build,再次执行docker-compose up即可

自动生成服务名的规则、以及其他的一些默认规则:
image.png

docker imgaes。执行了 docker-compose up 命令后,会自动 build 或下载镜像
image.png

[root@kuangshen ~]# docker service ls
Error response from daemon: This node is not a swarm manager. Use "docker
swarm init" or "docker swarm join" to connect this node to swarm and try
again.

默认的服务名 文件名_服务名_num
num代表副本数量,服务redis服务 => 4个副本。集群状态下,服务都不可能只有一个运行实例,可能一个服务运行多个实例,比如一份代码跑了多个tomcat,可能是根据某种方式轮询,或者权重选择,总之如果有一个挂了,要保证系统的使用不受影响,使得系统可以有弹性、可以动态的把一个服务扩展到10个副本,保证系统的HA(高可用)、高并发。
未来用 k8s 可以使用 kubectl 中的 service 的负载均衡。

  1. 网络规则
    image.png
    比如我们把10个服务打包成一个项目(项目中的内容都在同个网络下,可以通过域名来互相访问)
    image.png
    一个项目的多个服务在同一个网络下,我们就可以直接通过域名使得服务互相访问。
    HA。
    停止该项目(中所有的服务/容器): docker-compose stopdocker-compose downctrl+c
    image.png

docker-compose
以前都是单个 docker run 启动容器。
现在可以通过 docker-compose 编写 yaml配置文件、可以通过 compose 一键启动、停止所属某个项目的所有服务。

Docker小结:

  1. Docker 镜像: run 镜像后,运行为一个容器
  2. 使用 DockerFile 构建镜像(把一个服务打包成一个镜像,也就是把一个个服务打包成一个个镜像)
  3. 使用 docker-compose 启动或停止项目(编排、启动/停止、多个微服务/环境)
  4. 需要精通 Docker 网络

docker-compose.yaml 规则
https://docs.docker.com/compose/compose-fifile/#compose-fifile-structure-and-examples

docker-compose.yaml 有且只有3层配置:

  1. 第一层: 版本 - version
  2. 第二层: 服务 - services
  3. 第三层: 其他配置,比如网络/卷、全局规则等 - volumes、networks、configs ...
version: '' # compose file format 的版本,可以向上兼容的。比如 3.4 版的 docker-compose.yaml 可以被 17.09.0 以上的版本读取。 
services: # 服务
  服务1: web # 服务配置
    images
    build
    network
    depends_on:
      - redis # 依赖于 redis 服务,先启动 redis 服务,再启动 web 服务
    ports:
      - "8080:8080"
    ...
  服务2: redis
    ...
  服务3: mysql
# 其他配置 网络/卷、全局规则
volumes:
networks:
configs:

如果服务之间有依赖,则先启动依赖的服务,比如 web 服务依赖 db 服务和 redis 服务,则先启动 db 服务和 redis 服务再启动 web 服务
image.png

学习,要掌握规律,只要多写,多看,多看官方文档和开源项目:

  1. 官网文档
    https://docs.docker.com/compose/compose-fifile/#specifying-durations
  2. 开源项目 compose.yaml
    redis、mysql、mq。

开源项目

博客项目
以前的博客项目使用过程: 下载程序、安装数据库、配置...
使用了 docker compose 的博客项目 => 一键启动即可。

官网使用 docker-compose 安装 wordpress 的例子: https://docs.docker.com/samples/wordpress/

  1. 下载项目(项目里有docker-compose.yaml)
  2. 如果项目运行需要一些文件,则使用 Dcokerfifile 打包这些文件到镜像。
  3. 文件准备齐全后,直接一键启动项目。
  • 后台启动容器: docker -d xxx
  • 后台启动项目: docker-compose up -d
    image.png

需要掌握: docker 基础, docker 原理、网络、docker compose服务、集群、错误排除、日志查看等

实战

使用 Java 项目实现上面的官方的 demo ,使用 redis 来计算接口被访问的次数。

  1. 编写 SpringBoot 微服务项目。
  2. 编写 dockerfifile ,给下面的 docker-compose.yml 构建镜像用
  3. 编写 docker-compose.yml 编排项目
  4. 使用 IDEA 的 Maven 的 lifecycle.package 将项目打包为 jar 包(或者先 clean 再 install ,最后再 package)(如果打包出来的 jar 文件只有几kb,说明项目打包是有问题,需要百度搜索解决),然后将打包的jar包和上面写的 dockerfifile 、 docker-compose.yml 文件,上传到服务器(需要先新建一个目录作为项目目录,再把文件上传到该目录中),上传完成后执行 docker-compose up即可构建镜像

小结:
未来项目只要有 docker-compose.yml 文件,可以直接执行docker-compose up启动项目,网上开源项目也一样。

如果说启动失败,或项目要重新部署打包,则执行 docker-compose up --build # 重新构建项目

Docker Swarm 集群

购买服务器

购买阿里云服务器说明:
购买时余额可能需要大于100,不然买不了。
买4台1核2G(1核1G的安装个Docker会卡死)的服务器,如果是学k8s就得买4台2核4G不然跑不起来,一个月的学时,仅购买服务器的费用大约得2000。
点击创建实例购买,要选按量付费(按带宽、配置、时长计费,适合短时间学习使用,用完要释放才停止计费),实例选共享型的便宜,且买的这4台服务器必须要在同一个安全组中(同一个网络/网卡下),不然ping不通,内网中的服务器互相ping不需要流量可以省钱速度快还,阿里云一个区域的都算是一个内网,比如杭州区的服务器都算是在一个局域网内,一个安全组算是一个虚拟网卡,买多个服务器的话建议"自定义购买",登录凭证也是使用"自定义密码",可以设置成一样的密码,便于几台服务器的统一管理。实例名称可以使用默认即可,或自定义都可以。
image.png
image.png
image.png
到此服务器购买完毕。搭建一个1主,3从的集群。

4台机器安装 Docker

安装过程和我们单机安装 Docker 一样
技巧: 使用 xshell 的命令行右键选"发送输入到所有回话",可以输入一次命令,操作多台机器,直接同步操作,省时间精力金钱。

Docker Swarm 的工作模式

有管理节点和工作节点,管理节点之间可以互相通信,管理可以控制工作节点,工作节点不能控制管理节点。对操作都在manager,好比redis中,只能操作主机,而不能直接操作从机,从机只是备份的作用。集群中管理节点2个是没有意义的,一定要有3个以上。
image.png

搭建集群

因为是新买的服务器,所以docker网络只是默认的。一会儿搭建完成后会多出来一个网络。Docker Swarm 的命令基本就是增删改查:
image.png

可以使用 docker sawrm init --help来查看对应命令怎么使用:
image.png

阿里云服务器中有私网、公网,公网是对外开放的,私网是给局域网内访问的。
查看自己的私网ip地址
image.png
用自己的私网ip地址作为 docker swarm init命令的参数:
image.png
初始化节点命令: docker swarm init
在第二台服务器执行加入节点命令,将第二台服务器的docker加入第一台服务器: docker swarm join。如果要离开集群,就在对应的服务器上执行 docker sawrm leave即可退出集群节点。

# 获取令牌
方法1: docker swarm join-token manager # 使用该命令可以在主节点服务器生成一个命令,然后把生成的命令粘贴到其他服务器中即可将其他服务器加入节点作为【主节点】

方法2: docker swarm join-token worker # 使用该命令可以在主节点服务器生成一个命令,然后把生成的命令粘贴到其他服务器中即可将其他服务器加入节点作为【从节点】

image.png
把所有节点(服务器)都加进第一个节点后,在第一个节点(也就是主节点,主节点就是下图MANAGER STATUS为Leader或Rachable的节点)查看节点,可以看到所有节点
image.png
集群搭建完毕

步骤:

  1. 生成主节点 docker swarm init
  2. 给主节点加入新节点(管理者、worker)
    目标:双主双从(这样不科学,一般集群至少需要3个主节点)

Raft协议

双主双从: 假设一个节点挂了,其他就不能用了,可以在任意一个主节点执行 docker stop,测试停止一个主节点,只剩一个主节点,发现不能系统不能用了。

Raft协议: 保证大多数节点存活才可以用,只要>1才能用 ,集群至少大于3台。

实验:

  1. 将docker1机器停止,模拟宕机。 发现双主,另外一个主节点也不能使用了。
    image.png
  2. 执行docker sawrm leave可以所执行命令的服务器离开集群节点
    image.png
  3. work节点(从)就是工作的、管理命令只能在管理节点操作。 目前已经将3台机器设置为了管理节点。

集群中有3个主节点时,且随时要保证主节存活数量大于1(也就是至少2台),系统才可以正常使用,比如3个主节点,如果一个挂了还有2个可以用,如果再挂一个,还剩一个主节点就不能用了。
Raft协议: 保证大多数节点存活,才可以使用,高可用。

体会

集群: swarm docker serivce
docker swarm 可以把容器作为服务=> 服务还有多个副本,实现动态的集群(即主节点如果崩了一个,其他主节点也可以顶上)
体验:创建服务、动态扩展服务、动态更新服务。
image.png
灰度发布,即金丝雀发布,实现平滑升级代码。
image.png
启动服务后,可以使用 docker serivce ps my-nginx 查看docker的指定的服务,或者查看服务列表docker serivce ls

docker run 启动容器,不具有扩缩容功能
docker service 服务,具有扩缩容器,滚动更新项目、灰度发布功能。

查看服务docker serivce ps 服务名 REPLICAS
image.png
动态扩缩容: docker service update --relicas 3 <my-nginx(服务名)>即可在集群的节点(服务器)中随机创建3个(也可以是任意个)副本服务。
image.png
服务在,集群中任意的节点都可以访问,服务可以有多个副本动态扩缩容实现高可用。

可以使用 docker 虚拟出10台、10000台服务器卖/租给别人, 虚拟化。
docker可以实现服务的高可用,任何企业都可以玩云服务。

scale命令扩缩容,如下图就是扩缩容5份(扩容或缩容,如果当前小于5份执行命令后就是扩容,大于5分执行命令后就是缩容),使用docker service scale my-nginx=5命令和上面写的docker service update --relicas 5 <my-nginx(服务名)>效果是一样的。
image.png

移除服务: docker service rm my-nginx

docker swarm 其实并不难,只要会搭建集群、会启动服务、动态管理容器就可以了。

概念总结

  • swarm: 就是集群的管理和编排。 docker可以初始化一个 swarm 集群,其他节点可以加入。(加入节点是,有管理、工作者两个角色可以选择)
  • Node: 就是一个docker节点,多个节点就组成了一个网络集群,该集群的管理者就是 swarm 。(节点有管理或工作者两种角色)
  • Service: 就是一个运行的任务,可以在管理节点或者工作节点来运行,就是集群中的核心,用户访问和管理的就是管理的服务。
  • Task: 容器内的命令,也就是更为细节任务

如图,web 服务的 deploy 的 replicas: 4,所以 servie 就是创建了4份,这些副本运行在哪些服务器或者跑在哪些镜像上,不是我们关心的,这个过程是 docker swarm 随机给我们分配的
image.png
image.png
image.png

流程
输入命令 -> 管理节点 -> api -> 调度任务 -> 工作节点(自动创建Task容器)

服务副本与全局
如下图,管理节点和工作节点都可以运行任务,可以自己指定是否运行任务
image.png

调整service以什么方式运行:

--mode string
# Service mode (replicated or global) (default "replicated")
docker service create --mode replicated --name mytom tomcat:7 # 默认模式就是 replicated
docker service create --mode global --name haha alpine ping baidu.com
# 场景: 日志收集
# 每一个节点有自己的日志收集器,过滤。把所有日志最终再传给日志中心服务监控,状态性能。

拓展:
使用docker inspect 容器名可以看到有一个"... EndpointSpec 中的 Ports 中的 PublishMode ",这个就是容器/服务的网络模式,该值可以为

  • Swarm:
  • Overlay:
  • ingress: 默认就是这个值,ingress 就是一个特殊的 Overlay 网络,它具有负载均衡的功能,还有IPVS VIP(虚拟网络ip)等功能。
    虽然docker是在4台机器上,但实际网络是同一个,即他们都是在一个局域网中。
    image.png
    使用一个网络,使得多个机器变成一个整体可以互相 ping 通

之后的章节的知识用的比较少了,都是用k8s来替代了,了解即可

Docker Stack

image.png
在单机上部署项目: 使用 docker-compose 单机部署项目。
在集群上部署项目: 使用Docker Stack。

可以执行 docker stack 看该命令的使用方法

# 单机部署项目 docker-compose
docker-compose up -d wordpress.yaml

# 集群部署项目
docker stack deploy wordpress.yaml

# docker-compose 文件
version: '3.4'
services: # 这里部署了2个服务
  mongo:
    image: mongo
    restart: always
    networks:
      - mongo_network
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 2 # 2个副本,单机下就不用写该配置了,没有意义
  mongo-express:
    image: mongo-express
    restart: always
    networks:
      - mongo_network
    ports:
      - target: 8081
      published: 80
      protocol: tcp
      mode: ingress
    environment:
      ME_CONFIG_MONGODB_SERVER: mongo
      ME_CONFIG_MONGODB_PORT: 27017
    deploy:
      restart_policy:
        condition: on-failure
      replicas: 1
networks:
  mongo_network:
    external: true

Docker Secret

安全管理用的,用于配置密码、证书。可以执行 docker secret 看该命令的使用方法
image.png

Docker Confifig

比如给多个容器做统一的配置,可以执行 docker config 看该命令的使用方法
image.png

Docker的学习方式:网上找案例跑起来试试,查看命令帮助文档 --help、官网。

拓展到K8S

云原生时代

云应用: 到云商店下载云应用,然后做一些配置就可以将一个大型项目跑起来了。


必须要掌握k8s,不然工资高不了,10台机器以上优先考虑使用k8s。
必须掌握的语言: Java、Go

必须掌握的go语言的理由:

  1. docker、k8s、etcd 都是 go 开发的,如果要学习这些项目源码就得要会 go
  2. go 是并发语言
  3. go 语言的效率接近于C语言
  4. go 语言是多为大神级的大佬开发的,可见有多重要。

go 语言是 B语言、C语言的创始人、Unix创始人,V8
Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC(垃圾回收),结构形态及CSP-style 并发计算。
go 指针。
罗伯特·格瑞史莫(Robert Griesemer),罗勃·派克(Rob Pike)及肯·汤普逊(Ken Thompson)于
2007年9月开始设计Go,稍后Ian Lance Taylor、Russ Cox加入项目。Go是基于Inferno操作系统所开
发的。Go于2009年11月正式宣布推出,成为开放源代码项目,并在Linux及Mac OS X平台上进行了实
现,后来追加了Windows系统下的实现。在2016年,Go被软件评价公司TIOBE 选为“TIOBE 2016 年最
佳语言”。 目前,Go每半年发布一个二级版本(即从a.x升级到a.y)

Q.E.D.


做一个热爱生活的人