Skip to content

Docker

image-20200606203315597

1.Docker 概述

1.1 参考资料

官方文档:https://docs.docker.com/docker-for-windows/

【官方文档超级详细】

仓库地址:https://hub.docker.com/

【发布到仓库,git pull push】

b 站教程:https://www.bilibili.com/video/BV1og4y1q7M4?

【这个教程非常简洁!且深入!基于企业应用场景!推荐!以下笔记都基于该课程】

前期基础

linux 基本命令,类似 cd,mkdir 等

1.2 Docker 为什么会出现?

产生的问题

问题:

我在我的电脑上可以运行!但是==不同电脑环境可能不同,有版本更新等因素,换个电脑就运行不了了==!对于运维来说,考验就十分大!

一款产品,开发和上线有两套环境,==应用环境的配置费时费力==,有时环境还不能跨平台,而且容易出问题

如果有集群还需要给==每一个机器都部署环境==,非常麻烦

尤其对于机器学习和深度学习的库更是如此,很可能存在版本问题、底层依赖冲突问题

所以发布项目时,不只是一套代码过去,而是==代码 + 环境整体打包==过去(使用 Docker)

所谓开发即运维,就是要保证系统稳定性,提高部署效率

传统到现代的转变

传统方式:开发只给 jar 包,环境运维来做

现代方式:开发要打 jar 包,部署上线,一套流程做完!

使用 Docker 后的流程:

1.开发:编写代码(java) + 环境 ——> 打包带上环境,即成为镜像 ——> 放到 Docker 仓库(就相当于是应用商店)

2.部署:下载 Docker 中的镜像,直接运行即可(从应用商店下载应用)

Docker 核心思想

Docker 的思想来自于集装箱,集装箱意思就是对环境进行隔离

隔离是 Docker 的核心思想!打包装箱,每一个箱子是互相隔离的!不会存在端口冲突的情况,因为每个箱子都带有自己的环境,这些环

境即使有一样的,他们所用的端口肯定也是相互区分开的。(不用再依托于系统的环境,麻烦,可能还需要改端口等)

Docker 通过隔离机制,可以将服务器利用到极致,只要有空间就可以去运行东西。

Docker 给 DevOps(开发、运维)带来了什么?

1.应用更快速地交付和部署

传统:一堆帮助文档,安装程序

Docker:打包镜像,一键运行

2.更便捷地升级和扩缩容

使用了 Docker 之后,我们部署应用就和搭积木一样!

项目打包为一个镜像,可以直接扩展到 服务器 A!服务器 B!

3.更简单的系统运维

在容器化之后,我们的开发、测试环境是高度一致的!

4.更高效的计算资源利用

Docker 是内核级别的虚拟化,可以在一个物理机上运行很多的容器实例!服务器的性能可以被压榨到极致!

1.3 Docker 的历史

2010 年,几个搞 IT 的人,在美国成立一家公司dotCloud

做一些 paas 的云计算服务(平台即服务)

他们将自己的容器化技术命名为 Docker

Docker 基于Go 语言开发

Docker 刚刚诞生的时候,没有引起行业的注意,dotCloud 活不下去

然后他们决定开源:开放源代码

2013 年,创始人将Docker 开源,不开则以,一开惊人,刚开源的时候,每个月都会更新一个版本

2014 年 4 月 9 日,Docker 1.0 发布

1.4 容器 vs 虚拟机

1.4.1 虚拟机原理示意图

在容器技术出来之前,我们都用的是虚拟机技术!

虚拟机:在 win 中安装一个 VMware,通过这个软件我们可以虚拟出来一台或者多台电脑!但是很笨重!

虚拟机也是属于虚拟化技术!Docker 是容器技术,但是本质也是一种虚拟化技术!

image-20200606205436434

缺点

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

1.4.2 容器化技术示意图

不是模拟的完整的操作系统

image-20200606205739655

二者对比

比较虚拟机和 Docker 的不同:

bash
# vm :安装linux centos原生镜像(一整个电脑系统),我们要实现隔离需要开启多个虚拟机!——>相当于一台机器上面虚拟出来多台机器,相当于多台机器!
# docker:只需要安装镜像(最核心的环境,几个m,jdk+mysql),我们运行多个镜像就实现了隔离!——>相当于自始至终只有一台机器!
传统虚拟机Docker
虚拟内容硬件+完整的操作系统+软件APP+LIB
大小笨重,通常几个 G轻便几个 M 或 KB
启动速度慢,分钟级快,秒

1.5 Docker 安装

1.5.1 Docker 的基本组成

image-20200606212250845

明确几个概念:

  1. 镜像(image):docker镜像好比一个模板,可以通过这个模板来创建容器(container)一个镜像可以创建多个容器(最终项目运行是在容器中的),类似 Java 中的 Class ——> 应用

  2. 容器(container):Docker利用容器技术,独立运行一个或者一组应用,容器可以理解为一个简易的系统(一个 Linux 系统),类

    似 Java 中通过 Class 创建的实例对象 ——> 应用运行的结果(这个结果实际上就是真正用来运行我们的开发项目的)

  3. 仓库(repository):仓库就是存放镜像的地方,分为共有仓库和私有仓库(类似 github 存放项目) ——> 应用商店

    • Docker Hub:国外官方的

    • 阿里云容器服务:配置镜像加速!

1.5.2 环境准备

我们要有一台服务器,并且可以操作它

  1. Linux 命令基础,购买 linux 阿里云的服务器
  2. CentOS 7
  3. 使用 Xshell 链接远程服务器

1.5.3 安装 xshell

下载 CentOS7 https://www.jianshu.com/p/a63f47e096e8

下载 VMware 360 软件管家下载

VMware 配置虚拟机 https://blog.csdn.net/babyxue/article/details/80970526

xshell 链接服务器 https://blog.csdn.net/zzy1078689276/article/details/77280814

bash
[root@192 ~]# cd /
[root@192 /]# pwd
/
[root@192 /]# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
[root@192 /]# uname -r 查看系统内核
3.10.0-1160.45.1.el7.x86_64

1.5.4 在 Centos 安装 Docker

https://docs.docker.com/engine/install/centos/

1.卸载旧的 docker 版本

bash
# 卸载旧的版本
yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
image-20220717125738383

2.安装基本环境(一些安装 docker 需要的安装包)

bash
# 安装基本的安装包
yum install -y yum-utils
image-20220717132210523

3.设置镜像的仓库

注意!!下载默认用国外的,太慢了不要用!

用国内镜像,百度搜索,docker 的阿里云镜像地址

bash
# 不要用官网默认这个!
yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo # 默认是国外的

# 换成下面的
yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo # 阿里云镜像

image-20220717132343872

4.更新 yum 软件包索引

yum makecache fast
image-20220717132418017

没有问题的话就是可以用的

5.安装 docker 引擎

python
yum install docker-ce docker-ce-cli containerd.io # docker-ce 社区版 ;-ee 企业版
image-20220717132728968

6.启动 Docker

bash
systemctl start docker # 启动
bash
docker version #判断启动是否成功!

代表启动成功:

image-20220717132904633

注:还有一个重启 Docker

bash
systemctl restart docker

7.测试运行 helloworld

bash
docker run hello-world
image-20220717133111712

中间一堆是签名信息

1.5.5 run 命令的运行流程

run 命令就是用来运行镜像的!

image-20200616161441669

查看已经下载的镜像

docker images

image-20200616151913277

1.6 Docker 卸载

bash
# 卸载依赖
yum remove docker-ce docker-ce-cli containerd.io

# 删除docker运行环境
rm -rf /var/lib/docker # docker 的默认工作路径

1.7 配置阿里云镜像加速

刚刚我们只是配置了镜像仓库,但是还可以让镜像加速!下载镜像更快!阿里云的是免费的(但是腾讯云不免费)

操作:

image-20200616154429105

控制台搜索:容器镜像服务

image-20200616155201285

找到加速地址

image-20200616155649476

在配置文件中进行配置:

bash
mkdir -p /etc/docker # 创建一个文件夹

# 编写配置文件
tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://uyfgafsw.mirror.aliyuncs.com"]
}
EOF
#到这为止

systemctl daemon-reload # 重启服务
systemctl restart docker # 重启docker
image-20200616160315298

1.8 Docker 底层原理

Docker 是怎么工作的?

Docker 是一个 Client-Server 结构的系统,Docker 的容器运行在主机上的后台守护进程上,通过 Socket 从客户端访问!

DockerServer 接受到 Docker-Client 的指令(这个指令是我们程序员发起的),然后就会启动容器

image-20200616162107363

Docker 为什么比 VM 快?

  1. Docker 有着比虚拟机更少的抽象层,没有操作系统这一层
  2. Docker 主要用的是宿主机的内核,vm 需要 Guest OS 整个电脑系统

image-20200616162302653

所以说新建一个容器的时候,docker 不需要像虚拟机一样重新加载一个操作系统内核,避免引导

简而言之就是,虚拟机需要重新做一个系统与硬件交互,而 docker 不需要,它可以直接调用宿主机的系统!

2.Docker 常用命令

2.1 帮助命令

bash
docker version # 显示docker的基本信息
docker info # 系统信息,镜像和容器的数量
docker 命令 --help # 全部信息

官网文档

image-20200616163338187

2.2 镜像命令

2.2.1 docker images 查看镜像

查看所有本地主机上的镜像:

bash
docker images
image-20200616172056530
bash
# 解释
REPOSITORY  # 镜像仓库源
TAG         # 镜像的标签
IMAGE ID    # 镜像的ID
CREATED     # 镜像的创建时间
SIZE # 镜像的大小

命令后面还可以带参数:

bash
--all , -a		Show all images (default hides intermediate images) # 显示所有
--digests		Show digests
--filter , -f		Filter output based on conditions provided
--format		Pretty-print images using a Go template
--no-trunc		Don’t truncate output
--quiet , -q		Only show numeric IDs # 只显示id

例:

image-20200616172651835

2.2.2 docker search 搜索镜像

搜索 hub 仓库中的镜像,相当于网页搜索(类似 maven 仓库搜索 jar 包,github 搜索项目)

镜像仓库

image-20200616173009473

比如搜索 mysql:

image-20200616173050756

搜索 hub 仓库中的镜像:

docker search mysql
image-20200616173308194
bash
docker search --help  #查看search的帮助文档
image-20200616173740981

可以添加的参数:

bash
# 解释
Options:
  -f, --filter filter   Filter output based on conditions provided
      --format string   Pretty-print search using a Go template
      --limit int       Max number of search results (default 25)
      --no-trunc        Don't truncate output
bash
docker search mysql --filter=STARS=3000 # 搜索出Stars大于3000的
image-20200616174440284

2.2.3 docker pull 下载镜像

下载镜像

bash
docker pull mysql # 下载mysql镜像,default tag,默认最新版latest
bash
[root@192 ~]# sudo systemctl daemon-reload
[root@192 ~]# sudo systemctl restart docker
[root@192 ~]# docker pull mysql
Using default tag: latest # 不写tag默认最新版
latest: Pulling from library/mysql
8559a31e96f4: Pull complete  # layer 分层下载,docker image的核心 联合文件系统
d51ce1c2e575: Pull complete
c2344adc4858: Pull complete
fcf3ceff18fc: Pull complete
16da0c38dc5b: Pull complete
b905d1797e97: Pull complete
4b50d1c6b05c: Pull complete
c75914a65ca2: Pull complete
1ae8042bdd09: Pull complete
453ac13c00a3: Pull complete
9e680cd72f08: Pull complete
a6b5dc864b6c: Pull complete
Digest: sha256:8b7b328a7ff6de46ef96bcf83af048cb00a1c86282bfca0cb119c84568b4caf6 #签名
Status: Downloaded newer image for mysql:latest
docker.io/library/mysql:latest # 真实的下载地址

# 即
docker pull mysql
# 等价于:
docker pull docker.io/library/mysql:latest
bash
# 指定版本下载
docker pull mysql:8.0.20

版本来自于官网,版本库https://hub.docker.com/_/mysql

image-20200617094339687image-20200617100948088
bash
docker images

此时查看镜像,可以看到新下载的两个(包括名字,版本,id,下载时间)

image-20200617101105899

2.2.4 docker rmi 移除镜像

remove images

bash
# 删除一个 可以通过名称 也可以指定id,-f参数表示删除所有
docker rmi -f 9cfcce23593a
# 删除多个 用空格分隔镜像id
docker rmi -f id id id
# 删除所有
docker rmi -f $(docker images -aq) # images -aq就是查所有镜像id,从而递归删除,$()里面可以写表达式
image-20200617102049613image-20200617102126526

2.3 容器命令

2.3.1 常用命令

说明:有了镜像才能创建容器,需要 linux 镜像

1.下载一个 centos 镜像来测试学习 docker pull

bash
docker pull centos
image-20200617103406974

2.新建容器并启动 docker run 参数 环境名/环境 id (只能用环境创建容器,不论如何都会新建一个容器)(用环境 id 创建更好)

bash
docker run [可选参数] 环境名

# 可选参数说明
--name= "Name" # 容器名字(我们自定义的),用于区分容器 ——> 但是如果我们没定义,那么默认就是随机分配的!可以通过ps查看
-d  #以后台方式运行
-it #使用交互方式运行,可以进入容器查看内容
-p  #指定容器的运行端口 如-p 8080::8080
#-p旗下有四种使用方式:
	#-p 主机ip地址:主机端口:容器端口
	#-p 主机端口:容器端口 ——> 最常使用的
	#-p 容器端口
	#容器端口

-P #-大P,随机指定端口

3.新建容器并进入容器 docker run -it 环境名/环境 id /bin/bash

bash
# 进入
docker run -it centos /bin/bash #以交互方式启动centos,并指定控制台类型为bash!
# 查看目录
ls
# 停止运行并退出容器
exit

启动并进入容器:前面由主机名变成了容器 id!

image-20220717223428412

centos 里面是一个容器 centos,套娃,但是里面这个 centos 有很多命令都是不完整的!

4.查看运行的容器 docker ps -a

bash
# 查看正在运行的容器
docker ps
# -a 查看正在运行和曾经运行的容器
docker ps -a
# -n=? 显示最近创建的容器,设置显示个数
docker ps -a -n=?
# -q 只显示容器的id这个字段
docker ps -aq
shell
[root@192 ~]# docker ps  #退出之后就不显示运行了
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[root@192 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
9939864fa2e6        centos              "bin/bash"          4 minutes ago       Exited (0) 4 minutes ago                       unruffled_knuth
5f42e9930435        centos              "/bin/bash"         8 minutes ago       Exited (0) 4 minutes ago                       lucid_cannon
a89ddb393d3d        bf756fb1ae65        "/hello"            19 hours ago        Exited (0) 19 hours ago                        gracious_bhabha
[root@192 ~]# docker ps -a -n=2
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
9939864fa2e6        centos              "bin/bash"          6 minutes ago       Exited (0) 6 minutes ago                       unruffled_knuth
5f42e9930435        centos              "/bin/bash"         10 minutes ago      Exited (0) 7 minutes ago
[root@192 ~]# docker ps -aq
9939864fa2e6
5f42e9930435
a89ddb393d3d

5.在命令行里面停止运行容器,退出命令行,再次进入命令行(或者说直接进入容器命令行)

exit Ctrl + P + Q docker exec -it 容器 id /bin/bash

shell
# 容器停止运行并退出
exit
# 容器不停止运行并退出 注意必须在英文输入法下,中文输入法不行
Ctrl + P + Q  #键盘快捷键

docker exec -it 容器id /bin/bash #在不停止运行退出命令行的情况下,再次进入命令行
shell
[root@192 ~]docker run -it centos /bin/bash #按Ctrl + p + q
[root@bfcea13c40cd /] [root@192 ~] docker ps #这里会自动给个@主机名的前缀
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS              PORTS               NAMES
bfcea13c40cd        centos              "/bin/bash"         About a minute ago   Up About a minute                       stoic_wilson
edbd9366d959        centos              "/bin/bash"         7 minutes ago        Up 7 minutes                            affectionate_bartik
[root@192 ~]docker exec -it edbd9366d959 /bin/bash #再次进入命令行,用exec命令,并且必须使用容器id进入
[root@edbd9366d959 /] exit #停止并退出

6.删除容器 docker rm 容器 id

shell
# 删除指定容器 不能删除正在运行的容器,如果强制删除则使用 rm -f
docker rm 容器id
# 删除所有容器
docker rm -f $(docker ps -aq)
# 删除所有容器
docker ps -a -q|xargs docker rm
shell
[root@192 ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
bfcea13c40cd        centos              "/bin/bash"         29 minutes ago      Up 29 minutes                                   stoic_wilson
edbd9366d959        centos              "/bin/bash"         35 minutes ago      Up 35 minutes                                   affectionate_bartik
9939864fa2e6        centos              "bin/bash"          48 minutes ago      Exited (0) 48 minutes ago                       unruffled_knuth
5f42e9930435        centos              "/bin/bash"         52 minutes ago      Exited (0) 49 minutes ago                       lucid_cannon
a89ddb393d3d        bf756fb1ae65        "/hello"            20 hours ago        Exited (0) 20 hours ago                         gracious_bhabha
[root@192 ~]# docker rm 5f42e9930435
5f42e9930435
[root@192 ~]# docker rm edbd9366d959      # 注意正在运行的容器不能删除
Error response from daemon: You cannot remove a running container edbd9366d9596c744dd449119269b04de2f2a494e7fc471f6396bcefd94c33fe. Stop the container before attempting removal or force remove
shell
[root@192 ~]# docker ps -aq # 所有容器id
bfcea13c40cd
edbd9366d959
9939864fa2e6
a89ddb393d3d
[root@192 ~]# docker rm -f $(docker ps -aq) # 全部删除
bfcea13c40cd
edbd9366d959
9939864fa2e6
a89ddb393d3d

删除一个容器的最基本流程:先停止运行,然后删除

image-20220718015903105

7.在外面启动和停止容器的操作(已存在的容器) docker start/stop

shell
docker start +容器的id/name #开启容器运行
docker restart +容器的id/name #重启容器
docker stop +容器的id/name #停止容器运行,可以停止前台和后台运行的都可以
docker kill +容器的id/name #强制停止运行
shell
[root@192 ~]# docker run -it centos /bin/bash
[root@7b1a7dd10ea4 /]# exit
exit
[root@192 ~]# docker ps #查看正在运行的
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[root@192 ~]# docker ps -a # 查看历史运行过的
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
7b1a7dd10ea4        centos              "/bin/bash"         54 seconds ago      Exited (0) 42 seconds ago                       fervent_mirzakhani
[root@192 ~]# docker start 7b1a7dd10ea4 # 启动当前这个容器 container id 粘过 来
7b1a7dd10ea4
[root@192 ~]# docker ps # 查看当前运行容器 发现启动成功
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
7b1a7dd10ea4        centos              "/bin/bash"         2 minutes ago       Up 28 seconds                           fervent_mirzakhani
[root@192 ~]# docker stop 7b1a7dd10ea4 # 停止运行
7b1a7dd10ea4
[root@192 ~]# docker ps # 再次查看 没有这个容器了
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

2.3.2 其他常用命令

1.新建容器并在后台运行 docker run -d 镜像名

shell
docker run -d 镜像名

#小坑:
# 如果此时用docker ps 查看运行的容器,我们会发现容器运行停止了
# 原因:因为这个进程是后台运行的,前台没开进程,docker发现前台没有任何进程,也就是觉得自己没有提供服务,会立刻停止这个进程!
# 所以说,如果docker容器使用后台运行,那么就必须至少要有一个前台进程!
shell
Last login: Wed Jun 17 19:47:35 2020
[root@192 ~]# systemctl start docker # 关机后docker就关闭了,需要启动docker
[root@192 ~]# docker run -d centos # 后台运行
8ce188e5fee31c2fac93c0a405ee1a95c38dbc50cb47c35b19c0039c27558ded #返回了容器的id
[root@192 ~]# docker ps -a # 查看正在运行和曾经运行的容器 -a是可以查到的!
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
8ce188e5fee3        centos              "/bin/bash"         19 seconds ago      Exited (0) 18 seconds ago                       tender_dirac
7b1a7dd10ea4        centos              "/bin/bash"         8 hours ago         Exited (0) 8 hours ago                          fervent_mirzakhani

2.查看日志 docker logs -tf --tail n 容器 id

shell
docker logs
docker logs -tf --tail n 容器id #--tail n 是读取日志的最后n行
image-20200617161744298
shell
# 运行一个
[root@192 ~]# docker run -it centos /bin/bash
[root@c2887d35c71d /]# [root@192 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
c2887d35c71d        centos              "/bin/bash"         57 seconds ago      Up 56 seconds                           vigorous_kare
# 查看日志,由于没有进行过操作,所以啥也没显示
[root@192 ~]# docker logs -f -t --tail 10 c2887d35c71d
^C # ctrl+c退出

# 运行centos并在里面加个shell脚本(相当于携带它启动容器)
[root@192 ~]# docker run -d centos /bin/sh -C "while true;do echo haowenhai;sleep 1;done" #每秒输出一次haowenhai
cb6d7fbc3f27a064137d58282de97b97365dea2705211ebfbad642079cc1b388

[root@192 ~]# docker ps # 发现多了一个带shell脚本的容器运行
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
cb6d7fbc3f27        centos              "/bin/sh -c 'while t…"   7 seconds ago       Up 6 seconds                            dreamy_almeida
c2887d35c71d        centos              "/bin/bash"              3 minutes ago       Up 3 minutes                            vigorous_kare

# 查看日志 发现隔一秒打印一条
[root@192 ~]# docker logs -f -t --tail 10 cb6d7fbc3f27
2020-06-17T12:02:11.293765084Z shenzai
2020-06-17T12:02:12.297675608Z shenzai
2020-06-17T12:02:13.301845582Z shenzai
2020-06-17T12:02:14.304800996Z shenzai
2020-06-17T12:02:15.307130238Z shenzai
2020-06-17T12:02:16.310574235Z shenzai
2020-06-17T12:02:17.312946923Z shenzai
2020-06-17T12:02:18.314841295Z shenzai
2020-06-17T12:02:19.317021705Z shenzai
2020-06-17T12:02:20.319670013Z shenzai
2020-06-17T12:02:21.322651649Z shenzai
2020-06-17T12:02:22.325466918Z shenzai
2020-06-17T12:02:23.327984704Z shenzai
2020-06-17T12:02:24.329656919Z shenzai

3.查看正在运行的容器信息(容器的元数据) docker inspect 容器 id

shell
[root@192 ~]# docker inspect cb6d7fbc3f27
[
    {
        # 容器的完整id
        "Id": "cb6d7fbc3f27a064137d58282de97b97365dea2705211ebfbad642079cc1b388",

        # 创建时间
        "Created": "2020-06-17T12:00:50.706906186Z",

        # 脚本位置
        "Path": "/bin/sh",

        # 运行的脚本
        "Args": [
            "-c",
            "while true;do echo shenzai;sleep 1;done"
        ],
        "State": {
            "Status": "running", # 状态,正在运行
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 1909, # 父进程id
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2020-06-17T12:00:51.093617477Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        },

        # 来源镜像
        "Image": "sha256:831691599b88ad6cc2a4abbd0e89661a121aff14cfa289ad840fd3946f274f1f",
        "ResolvConfPath": "/var/lib/docker/containers/cb6d7fbc3f27a064137d58282de97b97365dea2705211ebfbad642079cc1b388/resolv.conf",
        "HostnamePath": "/var/lib/docker/containers/cb6d7fbc3f27a064137d58282de97b97365dea2705211ebfbad642079cc1b388/hostname",
        "HostsPath": "/var/lib/docker/containers/cb6d7fbc3f27a064137d58282de97b97365dea2705211ebfbad642079cc1b388/hosts",
        "LogPath": "/var/lib/docker/containers/cb6d7fbc3f27a064137d58282de97b97365dea2705211ebfbad642079cc1b388/cb6d7fbc3f27a064137d58282de97b97365dea2705211ebfbad642079cc1b388-json.log",


        "Name": "/dreamy_almeida",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,

        # 主机配置
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            #略

        # 其他配置
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/3675586ebbd79cd72d2562a90c9380627a331c563724c0dac091f92600af4907-init/diff:/var/lib/docker/overlay2/7f79322e0f58d651a84a555dadd83d92537788172525945d3f538dd95dce336c/diff",
                "MergedDir": "/var/lib/docker/overlay2/3675586ebbd79cd72d2562a90c9380627a331c563724c0dac091f92600af4907/merged",
                "UpperDir": "/var/lib/docker/overlay2/3675586ebbd79cd72d2562a90c9380627a331c563724c0dac091f92600af4907/diff",
                "WorkDir": "/var/lib/docker/overlay2/3675586ebbd79cd72d2562a90c9380627a331c563724c0dac091f92600af4907/work"
            },
            "Name": "overlay2"
        },

        "Mounts": [], # 挂载

        # 基本配置
        "Config": {
            "Hostname": "cb6d7fbc3f27",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
            ], # 基本环境变量,这里没有Java

            # 基本命令
            "Cmd": [
                "/bin/sh",
                "-c",
                "while true;do echo shenzai;sleep 1;done"
            ],
            "Image": "centos",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": null,
            "OnBuild": null,
            "Labels": {
                "org.label-schema.build-date": "20200611",
                "org.label-schema.license": "GPLv2",
                "org.label-schema.name": "CentOS Base Image",
                "org.label-schema.schema-version": "1.0",
                "org.label-schema.vendor": "CentOS"
            }
        },

        # 网卡,比如现在用的是桥接的网卡
        "NetworkSettings": {
            "Bridge": "",
            "SandboxID": "4d701985d7e77aa153790b697b2f38a61e20555c224b7675e4bf650b82799882",
            "HairpinMode": false,
            "LinkLocalIPv6Address": "",
            "LinkLocalIPv6PrefixLen": 0,
            "Ports": {},
            "SandboxKey": "/var/run/docker/netns/4d701985d7e7",
            "SecondaryIPAddresses": null,
            "SecondaryIPv6Addresses": null,
            "EndpointID": "8a6c71e2bafb19ca7dfd85445ccc4bef6d17467360a243d624089e676a24a018",
            "Gateway": "172.17.0.1",
            "GlobalIPv6Address": "",
            "GlobalIPv6PrefixLen": 0,
            "IPAddress": "172.17.0.3",
            "IPPrefixLen": 16,
            "IPv6Gateway": "",
            "MacAddress": "02:42:ac:11:00:03",
            "Networks": {
                "bridge": {
                    "IPAMConfig": null,
                    "Links": null,
                    "Aliases": null,
                    "NetworkID": "22b0fd2290ccbc4e066a75d3f01bd8bf32ee4352c5bbcfc9f911287219219571",
                    "EndpointID": "8a6c71e2bafb19ca7dfd85445ccc4bef6d17467360a243d624089e676a24a018",
                    "Gateway": "172.17.0.1",
                    "IPAddress": "172.17.0.3", #这个很重要,是容器的ip地址
                    "IPPrefixLen": 16,
                    "IPv6Gateway": "",
                    "GlobalIPv6Address": "",
                    "GlobalIPv6PrefixLen": 0,
                    "MacAddress": "02:42:ac:11:00:03",
                    "DriverOpts": null
                }
            }
        }
    }
]
shell
# 停止正在疯狂输出的那个容器
[root@192 ~]# docker stop cb6d7fbc3f27
cb6d7fbc3f27

4.进入当前正在运行的容器 docker exec -it 容器 id /bin/bash

shell
# 我们通常容器都是使用后台方式运行容器
docker exec -it 容器id bashSHELL(bash或者sh)

# 测试
[root@192 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
c2887d35c71d        centos              "/bin/bash"         35 minutes ago      Up 35 minutes                           vigorous_kare
#方式一:
[root@192 ~]# docker exec -it c2887d35c71d /bin/bash
[root@c2887d35c71d /]# ls
bin  etc   lib	  lost+found  mnt  proc  run   srv  tmp  var
dev  home  lib64  media       opt  root  sbin  sys  usr
[root@c2887d35c71d /]# ps -ef
UID         PID   PPID  C STIME TTY          TIME CMD
root          1      0  0 11:57 pts/0    00:00:00 /bin/bash
root         14      0  0 12:32 pts/1    00:00:00 /bin/bash
root         28     14  0 12:32 pts/1    00:00:00 ps -ef
[root@c2887d35c71d /]# exit

# 方式二:
[root@192 ~]# docker attach c2887d35c71d
[root@c2887d35c71d /]# exit

# 区别:
# docker exec # 进入容器后开启一个新的终端,可以在新的终端里面操作(常用)
# docker attach 进入容器正在执行的终端,不会启动新的终端

5.从容器内拷贝文件到主机上 docker cp 容器 id:文件路径 本机路径

shell
# 运行
[root@192 ~]# docker run -it centos
# ctrl P Q 不关闭退出,查看
[root@0569081aa89c /]# [root@192 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
0569081aa89c        centos              "/bin/bash"         19 seconds ago      Up 19 seconds
hopeful_chebyshev

# 查看主机home下无文件
[root@192 ~]# cd /home
[root@192 home]# ls

# 进入正在运行的容器
[root@192 home]# docker attach 0569081aa89c

# 进入容器的home目录
[root@0569081aa89c /]# cd /home

# 在目录中创建java文件
[root@0569081aa89c home]# touch test.java

# 退出并停止容器
[root@0569081aa89c home]# exit
exit

# 查看现在运行的容器
[root@192 home]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

# 因为exit了所以容器停止了,不过容器虽然被停止,但是数据都会保留!
[root@192 home]# docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                         PORTS               NAMES
0569081aa89c        centos              "/bin/bash"              3 minutes ago       Exited (0) 8 seconds ago                           hopeful_chebyshev

# 容器数据拷贝到主机
[root@192 home]# docker cp 0569081aa89c:/home/test.java /home
[root@192 home]# ls
test.java

# 拷贝是一个手动过程,未来我们使用-v卷的技术,可以实现自动同步 容器的/home 和本机的 /home

6.查看 Docker 中容器对于资源的占用情况(如 CPU 和内存等)

shell
docker stats

2.4 小结

常用命令关系图:

image-20200617210554147

常见命令大全:docker + 下面的这些命令

image-20200617210932306

image-20200617211021003

image-20200617211039508

3.Docker 实战

3.1 部署 Nginx

注:这里有个小坑

我们在运行 docker run -d --name nginx01 -p:3344:80 nginx 发生了下面的错误!

image-20220718012456526

bash
原因:在我们启动了Docker后,我们再对防火墙firewalld进行操作的话(更改了暴露端口等),就会发生上述报错,

详细原因:docker服务启动时定义的自定义链DOCKER,当 centos7 firewall 被清掉时,

firewall的底层是使用iptables进行数据过滤,建立在iptables之上,这可能会与 Docker 产生冲突。

 firewalld 启动或者重启的时候,将会从 iptables 中移除 DOCKER 的规则,从而影响了 Docker 的正常工作。

当你使用的是 Systemd 的时候, firewalld 会在 Docker 之前启动,但是如果你在 Docker 启动之后操作 firewalld ,你就需要重启 Docker 进程了。

解决方法:输入指令  systemctl restart docker     重启docker服务及可重新生成自定义链DOCKER

Nginx 用于做负载均衡和反向代理!

image-20200618100621199

下载镜像并创建重启并启动:

shell
# docker hub官网搜索nginx,可以看到详细信息,还可以看到帮助文档

# 下载镜像
[root@192 home]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
8559a31e96f4: Pull complete
8d69e59170f7: Pull complete
3f9f1ec1d262: Pull complete
d1f5ff4f210d: Pull complete
1e22bfa8652e: Pull complete
Digest: sha256:21f32f6c08406306d822a0e6e8b7dc81f53f336570e852e25fbe1e3e3d0d0133
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

# 查看镜像
[root@192 home]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              831691599b88        13 hours ago        215MB
nginx               latest              2622e6cca7eb        7 days ago          132MB

# 运行测试 docker run
# 参数说明:-d 后台运行,--name 容器命名,-p 暴露的端口号:3344(服务器开放的端口号,即宿主机的端口),容器内部的端口,nginx镜像名
# 注:3344是对于服务器的,是在服务器上的nginx的外部地址
# 80是nginx容器内部的默认端口,也就是说通过公网访问服务器的3344可以访问到nginx容器的80端口!!!
[root@192 home]# docker run -d --name nginx01 -p:3344:80 nginx
38dbf7bdcaef232d269b7184d91e44e06087181b5ee929494e177ad526810fa8
[root@192 home]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
38dbf7bdcaef        nginx               "/docker-entrypoint.…"   7 seconds ago       Up 6 seconds        0.0.0.0:3344->80/tcp   nginx01

# 使用3344可以访问成功
[root@192 home] curl localhost:3344 #测试一个链接

访问 nginx 成功:

image-20220718013019106

端口暴露的概念(容器与宿主机之间的端口连接策略,形成了一个映射关系!)

容器里面是 80

宿主机是 3344

-p 就是指定端口映射的参数

image-20200617212310709

可以公网访问这个端口,也就是说我们除了服务器本机可以访问,我们用本地 win 系统也可以!

浏览器输入 124.220.15.95:3344/,测试成功!

image-20220718013348181

进入 nignx 容器

shell
[root@192 home]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
38dbf7bdcaef        nginx               "/docker-entrypoint.…"   21 minutes ago      Up 21 minutes       0.0.0.0:3344->80/tcp   nginx01

# 进入容器
[root@192 home]# docker exec -it nginx01 /bin/bash

# 查一下nginx在哪
root@38dbf7bdcaef:/# whereis nginx
nginx: /usr/sbin/nginx /usr/lib/nginx /etc/nginx /usr/share/nginx

# 到这个目录,查看nignx的配置文件
root@38dbf7bdcaef:/# cd /etc/nginx
root@38dbf7bdcaef:/etc/nginx# ls
conf.d		koi-utf  mime.types  nginx.conf   uwsgi_params
fastcgi_params	koi-win  modules     scgi_params  win-utf

# 退出
root@38dbf7bdcaef:/etc/nginx# exit
exit

# 停止容器(其实这步没必要了)
[root@192 home]# docker stop 38dbf7bdcaef
38dbf7bdcaef

再次刷新网页,发现进不去了,服务关闭

思考问题

正常情况下我们每次改动 nginx 配置文件,都还需要进入容器内部,十分麻烦,是否可以在容器外部提供一个映射路径,达到

在容器外部修改文件,容器内部就可以自动修改呢?-v 数据卷技术!

3.2 部署 tomcat

image-20200618100551587

在 docker hub 上查看版本号和使用方法

image-20200618100319796

官方文档一定要翻烂,超多版本

官方文档的下载方法

shell
docker run -it --rm tomcat:9.0
# 命令
# 使用 docker run 命令其实 可以不用pull,能自动下载!!!——>省去了docker pull命令
# -it 直接进入容器
# --rm 是什么意思?入门的意思?——> 其实不是
# 我们之前的启动都是后台运行,ps停止了容器运行之后,容器也还是可以查到(还是存在的,没有被删除)
# 但是写了--rm的话,类似阅后即焚模式,停止了容器之后即删除容器,我们用ps -a就查不到了,这种通常用来测试
# 最后冒号指定版本号
image-20200618101811914

ctrl+c 可以退出容器

shell
docker ps -a
image-20200618102022167

可以看到并没有 tomcat,印证阅后即焚模式,容器会删除,但是镜像不会删除!

但是平时不建议这样搞!

正常方法下载并启动

shell
docker pull tomcat:9.0 # 之前下过了,就可以不用下了
image-20220718015320108

==注:这里是最正规的启动加进入容器的方法!!!==

也就是先通过-d 后台运行容器,然后再通过 exec -it 进入容器 ——> 这是最好的方法!

shell
# 启动运行,应该加上版本号
docker run -d -p 3355:8080 --name tomcat01 tomcat
#或者docker run -d -p:3355:8080 --name tomcat01 tomcat
#但是我们实际上应该docker run -d -p:3355:8080 --name tomcat01 tomcat:9.0 不然会去下载最新版的
image-20200618102837397
shell
# 进入容器
docker exec -it tomcat01 /bin/bash
image-20200618103109004

发现问题:

  1. linux 上可以使用的 tomcat 命令少了
  2. webapps 这个目录没有东西

image-20200618103407205

这是阿里云镜像(容器服务)的原因:默认使用最小的镜像,所有不必要的都剔除了,保证最小可运行环境

image-20200618103848104

在本地浏览器中输入:http://124.220.15.95:3355/,测试成功!

image-20220718020820590

思考问题

我们以后部署 web 项目,如果每次都要进入容器是不是身份麻烦?我要是可以在容器外部提供一个映射路径,我们在外部放置项目,就自

动同步到内部的 webapps 就好了!

docker 容器是包括 tomcat + 网站代码 的(这样的话如果把容器删了,就什么都没了,就删库跑路了)

docker 如果里面放了 mysql 就更不得了了!——> 所以后面我们要解决!

3.3 部署 elasticsearch+kibana

kibana 是 ES 的一个可视化控制台!

image-20200618104950722

下载镜像并创建容器

shell
# es的问题:我们需要解决前两个
# es 暴露的端口很多
# es 十分耗内存
# es 的数据一般需要放置到安全目录!挂载

# 启动 elasticsearch 容器,自动下载镜像!
# 内外的映射是用的一样的端口
docker run -d --name elasticsearch01 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:7.6.2

# 我们发现变得特别卡,敲命令都费劲!因为内存占了非常多,容易造成服务器卡死!
# 查看内存占用情况
docker stats

image-20200618111713885

shell
# 发现内存已经用了69%,赶紧stop一下容器!
docker stop ba18713ca536

解决 ES 的问题:限制内存

image-20200618105057785

shell
# 通过 -e 可以进行环境的修改,实现限制内存!
# 最少占64m内存,最多占512m内存!
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

image-20200618113018622

没成功,为什么呢?

大佬:

“ES_JAVA_OPTS=-Xms64m -Xmx512m” 引号提前试试

原因是引号!!

所以这里有坑,应该写为:

bash
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

成功:

image-20200618115302656

我们用本机访问 124.220.15.95/9200 ,测试成功!

image-20220718023119077

此时查看 stats,发现内存占用在控制范围内:

image-20200618115149971

ctrl + C 退出,还要记得 stop 容器

image-20200618115921069

思考

用 kibana 连接 elasticsearch 怎么实现?

两个都是 Docker 中的容器,但是两个容器是互相隔离的,我们没有办法直接连接,用 localhost:对方 ip 是行不通的!

我们可以采用以下方案:

使用 Linux 内网 IP 进行连接,涉及了 Docker 网络原理!

image-20200618113556445

3.4 Portainer 可视化面板安装

Docker 的可视化面板

  • portainer(先用这个)
  • Rancher(CI/CD 时用) ——> 这个是最佳的

什么是 portainer?

Docker 图像化界面管理工具,提供一个后台面板供我们操作!

shell
# -v是挂载,要把容器的一些数据挂载到本机!
docker run -d -p 8088:9000 --restart=always -v /var/run/docker.sock:/var/run/docker.sock --privileged=true portainer/portainer

坑:

bash
# 挂载路径必须是 /var/run/docker.sock:/var/run/docker.sock,最后不能少了.sock

image-20200618114542622

访问外网的 8088 端口:用本机访问 124.220.15.95:8088/

image-20220718024050741

设置用户名 admine

设置密码

进入主页:

image-20220718024538661

连接 Docker

image-20220718025348162

可以查看镜像和容器:

image-20220718025455666

4.Docker 镜像

镜像是什么?

镜像是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容

包括代码、运行时、库、环境变量和配置文件

所有的应用,直接打包 docker 镜像,就可以直接跑起来

如何得到镜像:

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

4.1 镜像加载原理

模型:UnionFS 联合文件系统

UnionFs(联合文件系统):Union 文件系统(UnionFs)是一种分层、轻量级并且高性能的文件系统,他支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下( unite several directories into a single virtual filesystem)。

**Union 文件系统是 Docker 镜像的基础。**镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。

**特性:**一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

Docker 镜像加载原理

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

1.bootfs(boot file system)主要包含 bootloader 和 Kernel, bootloader 主要是引导加 kernel, Linux 刚启动时会加 bootfs 文件系统,在 Docker 镜像的最底层是 bootfs。这一层与我们典型的 Linux/Unix 系统是一样的,包含 boot 加載器和内核。当 boot 加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时系统也会卸载 bootfs。——> 系统启动都需要引导加载,有一个加载器和一个内核,Docker 的镜像也不例外,加载成功之后这个 bootfs 就不要了!

2.rootfs(root file system),在 bootfs 之上。包含的就是典型 Linux 系统中的**/dev,/proc,/bin,/etc 等标准目录和文件**。 rootfs 就是各种不同的操作系统发行版,比如 Ubuntu, Centos 等等。——> 镜像加载出来的容器就是一个小的虚拟机环境(如 centos)

image-20200618140242423

思考:平时我们安装进虚拟机的 CentOS 都是好几个 G,为什么 Docker 这里才 200M?

image-20220718171946897

对于个精简的 OS,rootfs 可以很小,只需要包合最基本的命令,工具和程序库就可以了,因为底层直接用 Host 的 kernel,自己只需要提供 rootfs 就可以了。由此可见对于不同的 Linux 发行版, boots 基本是一致的, rootfs 会有差別,因此不同的发行版可以公用 bootfs.

虚拟机是分钟级别,容器是秒级!

4.2 分层的理解

我们可以去下载一个镜像,注意观察下载的日志输出,可以看到是一层层的在下载

image-20220718203602989

思考:为什么 Docker 镜像要采用这种分层的结构呢?

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

查看镜像分层的方式:可以通过docker image inspect 命令

BASH
docker image inspect redis
[
    {
        "Id": "sha256:f9b9909726890b00d2098081642edf32e5211b7ab53563929a47f250bcdc1d7c",
        "RepoTags": [
            "redis:latest"
        ],
        "RepoDigests": [
            "redis@sha256:399a9b17b8522e24fbe2fd3b42474d4bb668d3994153c4b5d38c3dafd5903e32"
        ],
        "Parent": "",
        "Comment": "",
        "Created": "2020-05-02T01:40:19.112130797Z",
        "Container": "d30c0bcea88561bc5139821227d2199bb027eeba9083f90c701891b4affce3bc",
        "ContainerConfig": {
            "Hostname": "d30c0bcea885",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "6379/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "GOSU_VERSION=1.12",
                "REDIS_VERSION=6.0.1",
                "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.0.1.tar.gz",
                "REDIS_DOWNLOAD_SHA=b8756e430479edc162ba9c44dc89ac394316cd482f2dc6b91bcd5fe12593f273"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "#(nop) ",
                "CMD [\"redis-server\"]"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:704c602fa36f41a6d2d08e49bd2319ccd6915418f545c838416318b3c29811e0",
            "Volumes": {
                "/data": {}
            },
            "WorkingDir": "/data",
            "Entrypoint": [
                "docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {}
        },
        "DockerVersion": "18.09.7",
        "Author": "",
        "Config": {
            "Hostname": "",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "6379/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "GOSU_VERSION=1.12",
                "REDIS_VERSION=6.0.1",
                "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-6.0.1.tar.gz",
                "REDIS_DOWNLOAD_SHA=b8756e430479edc162ba9c44dc89ac394316cd482f2dc6b91bcd5fe12593f273"
            ],
            "Cmd": [
                "redis-server"
            ],
            "ArgsEscaped": true,
            "Image": "sha256:704c602fa36f41a6d2d08e49bd2319ccd6915418f545c838416318b3c29811e0",
            "Volumes": {
                "/data": {}
            },
            "WorkingDir": "/data",
            "Entrypoint": [
                "docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": null
        },
        "Architecture": "amd64",
        "Os": "linux",
        "Size": 104101893,
        "VirtualSize": 104101893,
        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/adea96bbe6518657dc2d4c6331a807eea70567144abda686588ef6c3bb0d778a/diff:/var/lib/docker/overlay2/66abd822d34dc6446e6bebe73721dfd1dc497c2c8063c43ffb8cf8140e2caeb6/diff:/var/lib/docker/overlay2/d19d24fb6a24801c5fa639c1d979d19f3f17196b3c6dde96d3b69cd2ad07ba8a/diff:/var/lib/docker/overlay2/a1e95aae5e09ca6df4f71b542c86c677b884f5280c1d3e3a1111b13644b221f9/diff:/var/lib/docker/overlay2/cd90f7a9cd0227c1db29ea992e889e4e6af057d9ab2835dd18a67a019c18bab4/diff",
                "MergedDir": "/var/lib/docker/overlay2/afa1de233453b60686a3847854624ef191d7bc317fb01e015b4f06671139fb11/merged",
                "UpperDir": "/var/lib/docker/overlay2/afa1de233453b60686a3847854624ef191d7bc317fb01e015b4f06671139fb11/diff",
                "WorkDir": "/var/lib/docker/overlay2/afa1de233453b60686a3847854624ef191d7bc317fb01e015b4f06671139fb11/work"
            },
            "Name": "overlay2"
        },
        "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 Linux16.04 创建一个新的镜像,这就是新镜像的第一层;如果在该镜像中添加 Python 包,就会在基础镜像层之上创建第二个镜像层;如果继续添加一个安全补丁,就会创健第三个镜像层该像当前已经包含 3 个镜像层,如下图所示(这只是一个用于演示的很简单的例子)。

在添加额外的镜像层的同时,镜像始终保持是当前所有镜像的组合,理解这一点。

image-20200618140907894

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

image-20200618140932621image-20200618153329894

文种情況下,上层镜像层中的文件覆盖了底层镜像层中的文件。这样就使得文件的更新版本作为一个新镜像层添加到镜像当中,Docker 通过存储引擎(新版本采用快照机制)的方式来实现镜像层堆栈,并保证多镜像层对外展示为统一的文件系统

Linux 上可用的存储引撃有 AUFS、 Overlay2、 Device Mapper、Btrfs 以及 ZFS。顾名思义,每种存储引擎都基于 Linux 中对应的文件系统或者块设备技术,井且每种存储引擎都有其独有的性能特点。

Docker 在 Windows 上仅支持 windowsfilter,一种存储引擎,该引擎基于 NTFS 文件系统之上实现了分层和 CoW [1]。

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

img

特点

Docker镜像都是只读的当容器启动时,一个新的可写层被加到镜像的顶部,这一层就是我们通常说的容器层(也就是我们创建出来的容器),容器层之下的都叫镜像层

image-20200618153855605

注意点:

1.最上面的容器层可以应用任何一个版本的镜像层(多个镜像层的文件之间进行组合):所以我们创建 tomcat 容器的时候,可以写

tomcat:9.0,也可以直接写 tomcat(默认最新版)

image-20220718030741003

2.镜像层再进行版本更新时,不需要重新完全下载一个新的镜像,只需要在原来的镜像层上面加一层即可,新版本的镜像可以和原版本

镜像共用文件,所以在下载新版本的镜像时速度会快一些,已有的文件就不用下载了(我们先下载 tomcat9.0,再下载 tomcat 最新版就

会快很多)

image-20220718030631617image-20220718030609196

4.2 commit 提交镜像

注:是提交容器成为镜像!

相关命令

shell
docker commit # 提交容器成为一个新的镜像
docker commit -m="提交的描述信息" -a="作者" 容器id 目标镜像名:[TAG版本]

实操步骤

1.首先启动 tomcat 容器:

shell
docker images
docker run -it -p 9000:8080 tomcat #不要用8080!

image-20220718212849567

2.这是一个前台程序,所以说我们这个端口不能关!关了就断了,要新开一个面板!

进入容器

将 webapps.dist 里面所有的文件拷贝到 webapps 里面,其中-r 必须有,表示目录递归拷贝

image-20220718213225381

image-20220718214036816

3.提交

shell
# docker commit -a="paidaxing" -m="add webapps app" 当前容器的id tomcat02:1.0
docker commit -a="paidaxing" -m="add webapps app" f2860a478bad tomcat02:1.0

image-20220718214327199

发现新的版本,比之前的大了一些,因为里面记录了我们的改动!

如果想保存当前容器的状态(保存容器成为新的镜像),可以通过 commit 提交,获得一个镜像,这就好比我们以前学习 VM 的时候的快照

到这里算是入门了

接下来三个部分是 docker 的精髓

5.容器数据卷

诞生的背景

当我们在使用 docker 容器的时候,会产生一系列的数据文件,这些数据文件在我们关闭 docker 容器时是会消失的,但是其中产生的部分内容我们是希望能够把它

给保存起来另作用途的,Docker 将应用与运行环境打包成容器发布,我们希望在运行过程中产生的部分数据是可以持久化的的,而且容器之间我们希望能够实现

数据共享。

5.1 什么是容器数据卷?

docker 是要将应用和环境打包成一个镜像

这样,数据就不应该在容器中!如果数据都在容器中,那么我们容器删除,数据就会丢失,这就是删库跑路!需求:数据可以持久化

MySQL,容器删除了,删库跑路!需求:MySQL 数据可以存储在本地!

这样,数据就不应该在容器中!Docker 容器中产生的数据,同步到本地!

这就是卷技术!本质上是目录的挂载,将我们容器内的目录,挂载到 Linux 服务器上面!

image-20200618162917672

目的:容器的持久化和同步操作!容器间实现数据共享!

5.2 使用数据卷

5.2.1 方式一:直接使用命令挂载 -v

shell
docker run -it -v -p
# -it 交互式进入
# -v :volume卷技术
# -p 主机端口

1.挂载容器的目录到宿主机

bash
docker run -it -v /home/ceshi:/home/ centos /bin/bash #:前面是本机目录,:后面是容器目录

image-20200618163659503

2.新开一个窗口,查看容器信息

shell
docker inspect 容器id

image-20200618163938466

然后找到挂载信息 Mounts

image-20200618164148642

3.测试

我们在容器中修改文件,主机上面会变;我们在主机上修改文件,容器内同样会变!

image-20200618164818624

4.进一步测试:

1、停止容器

2、宿主机修改文件

3、启动容器

4、容器内的数据依旧是同步的

——> ==双向同步!==

总结

好处:我们以后修改只需要在本地修改即可,容器内会自动同步!

5.3 实战安装 mysql

使用数据卷技术令容器中的 MySQL 的数据持久化!!!

步骤

1.安装,运行

shell
docker search mysql

# 拉取
docker pull mysql:5.7 #如果本来就有就不需要拉取了

# 创建容器并运行,并挂载目录
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:8.0.20

-d 后台运行
-p 端口映射
-v 卷挂载
-e 环境配置 安装启动mysql需要配置密码
--name 容器名字

2.链接测试:打开 Navicat,连接成功

image-20220718230959391

3.我们在 navicat 中新建一个 test 数据库(这里操作的是服务器本机的挂载目录)

发现多了 test 文件

image-20220718231453345

4.我们进入 mysql 容器查看 docker exec -it 容器 id /bin/bash

发现容器的/var/lib/mysql 文件夹下面也多了一个 test 文件夹

image-20220718232013698

5.我们把这个包含 mysql 的容器删除!

发现,我们挂载到本地的数据卷依旧没有丢失,这就实现了容器数据的持久化功能

5.4 具名挂载和匿名挂载

bash
# 一、匿名挂载
# -v 容器内路径! -P:表示随机映射端口
$ docker run -d -P --name nginx01 -v /etc/nginx nginx

# docker volume ls 查看本机的所有的volume(卷)的情况 (不管是怎么挂载的都显示)
$ docker volume ls
DRIVER              VOLUME NAME  # 容器内的卷名(匿名卷挂载,或者全路径挂载的会显示为随机码,即下面这样!因为我们没有指定名字)
local               21159a8518abd468728cdbe8594a75b204a10c26be6c36090cde1ee88965f0d0
local               b17f52d38f528893dd5720899f555caf22b31bf50b0680e7c6d5431dbda2802c

# 这里发现,这种就是匿名挂载,我们在 -v 的参数中只写了容器内的路径,没有写容器外的路径!被分配了一个随机名

# 二、具名挂载
$ docker run -d -P --name nginx02 -v juming-nginx:/etc/nginx nginx
9663cfcb1e5a9a1548867481bfddab9fd7824a6dc4c778bf438a040fe891f0ee

# docker volume ls 看所有的volume(卷)的情况
$ docker volume ls
DRIVER              VOLUME NAME # 容器内的卷名(具名挂载的显示的是我们自定义的卷名)
local               21159a8518abd468728cdbe8594a75b204a10c26be6c36090cde1ee88965f0d0
local               b17f52d38f528893dd5720899f555caf22b31bf50b0680e7c6d5431dbda2802c
local               juming-nginx #多了一个名字
#如果是匿名挂载,我们在 -v 除了写容器内的路径,还指定了一个卷名


# docker volume inspect 查看一下这个卷的具体信息,以及它位于本机上的什么位置
$ docker volume inspect juming-nginx
[
    {
        "CreatedAt": "2020-05-23T13:55:34+08:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/juming-nginx/_data", #默认目录
        "Name": "juming-nginx",
        "Options": null,
        "Scope": "local"
    }
]

所有的 docker 容器内的卷(不管是匿名挂载还是具名挂载),在没有指定目录的情况下挂载都是在**/var/lib/docker/volumes/自定义的卷名/_data**下,如果指定了目录,docker volume ls 也可以查看到

我们通过具名挂载可以很方便地查找到我们的卷,所以大多数情况我们使用具名挂载!

5.5 区分三种挂载方式

匿名挂载、具名挂载、指定路径挂载

-v 容器内路径 # 匿名挂载 , 会分配随机名字 -v 卷名:容器内路径 # 具名挂载 , 我们指定的名字就是卷名 -v /宿主机路径:容器内路径 # 指定路径挂载 , 会分配随机名字

拓展

通过 -v 容器内路径:ro/rw 改变读写权限(是相对于容器来说的

ro : readonly 只读 rw : readwrite 可读可写 ——> 默认的

bash
$ docker run -d -P --name nginx05 -v juming:/etc/nginx:ro nginx #只读
$ docker run -d -P --name nginx05 -v juming:/etc/nginx:rw nginx #可读可写

ro:只要看到 ro 就说明这个挂载文件只能通过宿主机来操作,容器内部是无法操作的,容器内部只能读,但是宿主机可以任意操作!

5.6 在 Dockerfile 中指定数据卷

Dockerfile 就是用来构建 docker 镜像的构建文件!(先当于构造器)——> **命令脚本!**先体验一下!

通过这个脚本可以生成镜像,镜像是一层一层的,脚本是一个个的命令,每个命令都是一层!

步骤

1.创建 dockerfile 文件

bash
# 创建一个dockerfile文件,名字可以随便 建议使用dockerfile + 数字
vim dockerfile1

2.编写文件里面的内容:==注:这个文件里面不能写注释!!!大坑==

bash
# 文件中的内容: 指令(大写) + 参数
# 镜像是一层一层的,脚本是一行一行的
# 指令都是大写的
# 这里的每个命令可以理解为镜像的一层
FROM centos 					# 当前这个镜像是以centos为基础的 :每个镜像都必须有一个基于的操作系统!
VOLUME ["volume01","volume02"] 	 # 挂载卷的卷目录列表(多个目录),这两个目录会自动挂载到宿主机上成为卷(匿名挂载)
#听说上面这个volume01和volume02前面必须加/,否则创建失败!——> 坑
CMD echo "-----end-----"		# 输出一下用于测试
CMD /bin/bash					# 默认走bash控制台

3.利用 dockerfile 构造镜像:

bash
# 参数说明:
#-f dockerfile1 		# f代表file,指的是所用dockerfile文件的地址(这里是当前目录下的dockerfile1)
#-t haowenhai/centos 	# t就代表target,指目标目录,即镜像的名字!(注意haowenhai镜像名前不能加斜杠/)
#. 					  # 表示生成在当前目录下,最后的这个点很重要
docker build -f dockerfile1 -t haowenhai/centos .

#输出结果:
Sending build context to Docker daemon   2.56kB
Step 1/4 : FROM centos
latest: Pulling from library/centos
8a29a15cefae: Already exists
Digest: sha256:fe8d824220415eed5477b63addf40fb06c3b049404242b31982106ac204f6700
Status: Downloaded newer image for centos:latest
 ---> 470671670cac
Step 2/4 : VOLUME ["volume01","volume02"] 			# 卷名列表
 ---> Running in c18eefc2c233
Removing intermediate container c18eefc2c233
 ---> 623ae1d40fb8
Step 3/4 : CMD echo "-----end-----"					# 输出 脚本命令
 ---> Running in 70e403669f3c
Removing intermediate container 70e403669f3c
 ---> 0eba1989c4e6
Step 4/4 : CMD /bin/bash
 ---> Running in 4342feb3a05b
Removing intermediate container 4342feb3a05b
 ---> f4a6b0d4d948
Successfully built f4a6b0d4d948
Successfully tagged caoshipeng/centos:latest

4.查看自己构建的镜像

shell
docker images
image-20200618213310752

5.启动自己创建的镜像,创建 centos 容器

查看里面的目录

image-20200618220658979

这个挂载目录和外部一定有一个同步的目录(卷)

6.查看一下卷挂载的信息

bash
# docker inspect 容器id
$ docker inspect ca3b45913df5
image-20200618221801103

向下翻查看 Mounts,Source 字段,里面有容器内部文件对应的挂载目录,发现这是两个匿名挂载卷!

image-20200618221837895

7.测试一下,在 container volume01 下创建文件 container.txt

发现在主机挂载路径下,也同样生成了该文件!容器的文件挂载成功!

image-20200618222224352

5.7 数据卷容器:多个容器数据的共享

例如:多个 MySQL 同步数据!(但是实际生产都是用分布式,不会在一个机器上装两个 mysql,所以这个没什么意义)

在容器里面挂载数据卷(并且是在父容器里面挂载)!之前是在宿主机挂载数据卷!

**实现流程:**子容器 --volumes-from 父容器(数据卷容器)

利用这个容器给别的容器共享数据,实现数据的同步

image-20200621165403842

步骤

1.启动父容器,并挂载目录

并创建一个子容器继承父容器

bash
# 测试 :创建启动3个容器,通过刚才自己写的镜像进行启动
# 创建docker01:因为我本机是最新版,故这里用latest,狂神老师用的是1.0如下图 ,可以用docker images 查看镜像的版本号!
# 一般版本号是必须写的,不写就是默认用latest了!
$ docker run -it --name docker01 haowenhai/centos:latest

# 查看容器docekr01内容
$ ls
bin  home   lost+found	opt   run   sys  var
dev  lib    media	proc  sbin  tmp  volume01
etc  lib64  mnt		root  srv   usr  volume02

# 不关闭该容器退出(其实直接exit也可以,不影响)
CTRL + Q + P

# 创建docker02: 并且让 docker02 继承 docker01
$ docker run -it --name docker02 --volumes-from docker01 haowenhai/centos:latest

# 查看容器docker02内容
$ ls
bin  home   lost+found	opt   run   sys  var
dev  lib    media	proc  sbin  tmp  volume01
etc  lib64  mnt		root  srv   usr  volume02

2.可以验证,在 docker01 的挂载目录下加一个数据,在 docker02 下也会出现

image-20200621171513650

3.创建 docker03,同样继承 docker01

shell
docker run -it --name docker03 --volumes-from docker01 haowenhai/centos:latest

image-20200621172333639

4.在 docker03 的 volume01 下建立文件,在 docker01 的 volume01 下同样也有

结论 1

只要是子容器继承了父容器就拥有了它的挂载目录的使用权,这里面==一个父和两个子三者任何一方进行了修改,都会共享同步数据==!!!

5.删除父容器:docker01 容器(即数据卷容器,相当于之前的宿主机),进入 docker02 容器,发现挂载的数据依然在!

shell
docker rm -f docker01

image-20200621172830779

6.后面又经过测试发现,没有了父容器之后,docker02 和 docker03 之间依然可以实现数据的随意共享!

结论 2(最终结论)

容器之间共享挂载文件的情况下,删除其中任何一个容器都不会使文件丢失(即使是父容器)

原因:

因为实际上底层这里面的父容器里面的文件夹是挂载到宿主机的一个目录下的(我们在上一板块看到过了,是一个匿名挂载,因为我们用的是指定了数据卷

的 dockerfile 进行创建的镜像,并用那个镜像创建的这个父容器),关键在于由于子容器和其有及继承关系,所以子容器相当于也是挂载在了这个目录下面的,

所==以真实情况就是三者用的是同一个宿主机上面的挂载目录!==

而 --volumes-from 这句话的意思就是让子容器拥有父容器的挂载目录的使用权,三者共享一个挂载目录!!!

即使所有容器都被删除了,本地还是有这个挂载目录,文件不会丢失!

image-20220719015025038

所以说只要数据挂载到了本地,即使所有容器数据删除了,本地数据是还在的,这样数据可以持久化!

所以一般我们的策略是这样的

首先我们使用 Dockerfile 构建一个自己的容器,并指定挂载到宿主机上面。——> 实现数据持久化!

然后再启动几个容器使用--volumes-from 来跟启动的第一个容器挂载相同的目录来实现容器间的数据同步。——> 实现数据的共享与同步!

6.DockerFile

6.1 介绍

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

构建步骤:

  1. 编写一个 dockerfile 文件
  2. docker build 构建成为一个镜像
  3. docker run 运行镜像
  4. docker push 发布镜像(DockerHub、阿里云镜像仓库 私有/公有)

这个写一个项目时一样的

6.2 官方 DockerFile 示例

看一下官方的 DockerFile

img

点击后跳到一个 Dockerfile

image-20200621174204807

很多官方镜像都是基础包,很多功能没有,我们通常会自己搭建自己的镜像!

官方既然可以制作镜像,那我们也可以!

6.3 DockerFile 规范

  1. 每个指令都必须是大写字母
  2. 按照从上到下顺序执行
  3. *#*表示注释
  4. 每一个指令都会创建体检一个新的镜像层,并提交
image-20200621174948310

Dockerfile 是面向开发的,我们以后要发布项目,做镜像,就需要编写 dockerfile 文件,这个文件十分简单!

Docker 镜像逐渐成企业交付的标准,必须要掌握!

DockerFile:构建文件,定义了一切的步骤,源代码

DockerImages:通过 DockerFile 构建生成的镜像,用于最终发布和运行产品。

Docker 容器:容器就是镜像运行起来提供的服务。

6.4 DockerFile 的指令

查看源图像

bash
FROM				# from:基础镜像,一切从这里开始构建
MAINTAINER			# maintainer:镜像是谁写的, 姓名+邮箱
RUN					# run:镜像构建的时候需要运行的命令
ADD					# add:比如创建tomcat镜像,需要tomcat的压缩包和jdk压缩包,会自动解压!可以添加内容,添加解压缩目录
WORKDIR				# workdir:镜像的工作目录(在宿主机上的工作目录)
VOLUME				# volume:挂载的目录
EXPOSE				# expose:保留端口配置
CMD					# cmd:指定这个容器启动的时候要运行的命令,只有最后一个会生效,可被替代
ENTRYPOINT			# entrypoint:指定这个容器启动的时候要运行的命令,可以追加命令
ONBUILD				# onbuild:当构建一个被继承DockerFile这个时候就会运行onbuild的指令,触发指令
COPY				# copy:类似ADD,将我们文件拷贝到镜像中
ENV					# env:用于构建的时候设置环境变量!

理解 ADD 和 ENV

镜像就像是在宿主机上的寄生虫,其容器运行所需要的环境(ADD 添加的),解压缩之前是放在宿主机上的,解压缩之后才是放在容器里的,这个解压缩后的

环境只能由其容器内部来使用,其内部配置的环境变量(ENV 配置的)也只在容器启动的命令行里面有效!但其设置的工作目录,指的是容器内部的目录。

但是本质上镜像就是一个依托于宿主机的资源来完成自己的工作的一种虚拟环境。

6.5 实战构建自己的 centos:docker build -f dockerfile 名 -t 镜像名 .

scratch 镜像

bash
FROM scratch
ADD centos-7-x86_64-docker.tar.xz /

LABEL \
    org.label-schema.schema-version="1.0" \
    org.label-schema.name="CentOS Base Image" \
    org.label-schema.vendor="CentOS" \
    org.label-schema.license="GPLv2" \
    org.label-schema.build-date="20200504" \
    org.opencontainers.image.title="CentOS Base Image" \
    org.opencontainers.image.vendor="CentOS" \
    org.opencontainers.image.licenses="GPL-2.0-only" \
    org.opencontainers.image.created="2020-05-04 00:00:00+01:00"

CMD ["/bin/bash"]

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

1.创建目录结构

shell
# 创建一个自己的centos

# 进入home目录
cd /home
# 创建一个目录,之后的有关dockerfile的东西都保存到这里
mkdir dockerfile
# 进入这个目录
cd dockerfile/
# 再创建一个mudockerfile1目录
mkdir mydockerfile1
cd mydockerfile1/
# 创建一个dockerfile,名字叫mydockerfile-centos
vim mydockerfile-centos

2.xshell 新开一个界面测试一下官方 centos 镜像创建的容器有什么功能:

shell
# 官方默认的centos:
docker run -it centos
pwd # 官方默认有pwd命令
vim # 官方默认没有vim命令
ifconfig # 官方默认没有ifconfig命令
image-20200621184333206

3.回到 mydockerfile1 目录,编写 mydockerfile-centos

shell
# 下面给官方centos加上自定义的内容
FROM centos							# 基础镜像是官方原生的centos
MAINTAINER hao<1558637209@qq.com> 	  # 作者信息

ENV MYPATH /usr/local				# ENV设置工作目录路径(这个路径是容器内的)
WORKDIR $MYPATH						# 将工作目录设置为 MYPATH (linux宿主机的工作目录~是root)

RUN yum -y install vim				# 给官方原生的centos 增加 vim指令
RUN yum -y install net-tools		# 给官方原生的centos 增加 ifconfig命令

EXPOSE 80							# 暴露端口号为80

CMD echo $MYPATH					# 输出下 MYPATH 路径
CMD echo "-----end----"				#测试输出一句话
CMD /bin/bash						# 启动后进入 /bin/bash

4.下面通过这个这个文件创建镜像

注: -f 指定文件名

shell
docker build -f mydockerfile-centos -t mycentos:0.1 .

5.测试运行,创建容器

并测试以下命令是否可以使用:

shell
docker run -it mycentos:0.1 # 版本号必须写,不然他会去找最新的
pwd	#测试我们位于宿主机上的什么位置
#结果:/usr/local			# 与Dockerfile文件中 WORKDIR 设置的 MYPATH 一致
vim				# vim 指令可以使用
ifconfig     		# ifconfig 指令可以使用

这时可以看到这些功能都有了

6.可以通过查看 docker 构建历史

image-20200621192103460

可以看到当前这个镜像是怎么一步一步构建起来的

我们平时拿到一个镜像也可以通过 docker history 镜像id 这个方法研究一下他是怎么做的

6.6 CMD 与 ENTRYPOINT 的区别

bash
1.CMD					# 指定这个容器启动的时候要运行的命令,并且只有最后一个会生效,可被创建容器时指定的命令所替代
2.ENTRYPOINT			# 指定这个容器启动的时候要运行的命令,在创建容器时可以追加命令

6.6.1 测试 CMD

BASH
# 编写dockerfile文件
$ vim dockerfile-test-cmd
FROM centos
CMD ["ls","-a"]					# 启动后执行 ls -a 命令,启动容器之后展示目录

# 构建镜像
$ docker build  -f dockerfile-test-cmd -t cmd-test:0.1 .

# 运行镜像
$ docker run cmd-test:0.1		# 由结果可得,运行后就执行了 ls -a 命令,显示了目录结构!
.
..
.dockerenv
bin
dev
etc
home

# 目的:想追加一个命令  -l 成为ls -al:展示列表详细数据
$ docker run cmd-test:0.1 -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.
ERRO[0000] error waiting for container: context canceled

#但是我们发现失败了!,原因:cmd的情况下 -l 替换了CMD["ls","-a"]的ls -a 而 -l 不是命令所以报错了!

6.6.2 测试 ENTRYPOINT

bash
# 编写dockerfile文件
$ vim dockerfile-test-entrypoint
FROM centos
ENTRYPOINT ["ls","-a"]

# 构建镜像
$ docker build -f dockerfile-test-entrypoint -t cmd-test:0.1 .

# 运行镜像
$ docker run entrypoint-test:0.1
.
..
.dockerenv
bin
dev
etc
home
lib
lib64
lost+found ...
# run的时候可以直接加命令
# 我们的命令,是可以直接拼接在我们得ENTRYPOINT命令后面的,命令变为:ls -al
$ docker run entrypoint-test:0.1 -l
total 56
drwxr-xr-x   1 root root 4096 May 16 06:32 .
drwxr-xr-x   1 root root 4096 May 16 06:32 ..
-rwxr-xr-x   1 root root    0 May 16 06:32 .dockerenv
lrwxrwxrwx   1 root root    7 May 11  2019 bin -> usr/bin
drwxr-xr-x   5 root root  340 May 16 06:32 dev
drwxr-xr-x   1 root root 4096 May 16 06:32 etc
drwxr-xr-x   2 root root 4096 May 11  2019 home
lrwxrwxrwx   1 root root    7 May 11  2019 lib -> usr/lib
lrwxrwxrwx   1 root root    9 May 11  2019 lib64 -> usr/lib64 ....

Dockerfile 中很多命令都十分的相似,我们需要了解它们的区别,我们最好的学习就是对比他们然后测试效果!

6.7 构建自己的 Tomcat 镜像

1、准备镜像所需要的文件

在/home/mydockerfile/下面新建文件夹 docker-tomcat

准备 tomcat 和 jdk 到当前目录,编写好 README

可以通过 xftp 将安装包上传到 docker-tomcat 目录!

里面一共需要 tomcat 安装包、jdk 安装包、dockerfile、README 四个文件

img

2、编写 dokerfile(==这是一个官方命名,我们用了这个名字,在创建镜像的时候就不需要-f 去指定文件名了!==)

bash
$ vim dockerfile
FROM centos 									 # 基础镜像为centos
MAINTAINER hao<11558637209@qq.com>					# 作者

COPY README /usr/local/README 						# 复制README文件,从宿主机拷贝到centos容器上面的/uer/local文件夹下面
# ADD mkdir /usr/local                                  # 可以创建一下这个文件夹,防止宿主机上面没有没有
ADD jdk-8u231-linux-x64.tar.gz /usr/local/ 			# 添加jdk,ADD 命令会自动解压,解压缩到centos容器上面的/uer/local文件夹下面
ADD apache-tomcat-9.0.35.tar.gz /usr/local/ 		# 添加tomcat,ADD 命令会自动解压,解压缩到centos容器上面的/uer/local文件夹下面

RUN yum -y install vim								# 安装 vim 命令

ENV MYPATH /usr/local 								# ENV设置容器的工作目录的路径(这个路径实际上是centos容器上面的,linux宿主机中的工作目录是root)
WORKDIR $MYPATH									  # WORKDIR设置容器的工作目录

# 设置容器的环境变量在容器上面的目录路径:
ENV JAVA_HOME /usr/local/jdk1.8.0_231
ENV CLASSPATH $JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar # 据说这个可以不配置

# 设置容器的环境变量在容器上面的目录路径:
ENV CATALINA_HOME /usr/local/apache-tomcat-9.0.35
ENV CATALINA_BASE /usr/local/apache-tomcat-9.0.35

# 真正地设置容器的环境变量path,linux中分隔符是冒号:(这些配置会自动写入容器的那个配置文件里面)
ENV PATH $PATH:$JAVA_HOME/bin:$CATALINA_HOME/lib:$CATALINA_HOME/bin

EXPOSE 8080 										# 设置容器暴露的端口(即防火墙放出来的端口)

CMD /usr/local/apache-tomcat-9.0.35/bin/startup.sh && tail -F /usr/local/apache-tomcat-9.0.35/logs/catalina.out
# 设置默认命令 # 创建完容器后直接启动tomcat

3、构建镜像

bash
# 因为dockerfile使用了官方默认命名,因此不用使用-f 指定文件
$ docker build -t mytomcat:0.1 .

4、run 镜像,创建容器

bash
# -d:后台运行 -p:暴露端口 --name:别名 -v:挂载文件
$ docker run -d -p 9000:8080 --name tomcat01
-v /home/kuangshen/build/tomcat/test:/usr/local/apache-tomcat-9.0.35/webapps/test
-v /home/kuangshen/build/tomcat/tomcatlogs/:/usr/local/apache-tomcat-9.0.35/logs mytomcat:0.1

5、访问测试

bash
$ docker exec -it 自定义容器的id /bin/bash
$ cul localhost:9000

6、发布项目

由于做了数据卷挂载,我们直接在本地编写项目内容(比如把 javaweb 项目放到 webapps/test 目录下面,然后就可以发布镜像了!

我们放一个 WEB-INF/pom.xml 和 index.jsp 到本地 test 目录中

发现:项目部署成功,访问 124.220.15.95/9000/test 可以直接访问该项目!

我们查看本地/home/kuangshen/build/tomcat/tomcatlogs/下的 catalina.out 文件,就可以看到输出的日志了!

我们以后的开发:需要掌握 Dockerfile 的编写!我们之后的一切项目都是使用 docker 镜像来发布运行!

6.8 发布镜像

6.8.1 发布到 Docker Hub

1、地址 https://hub.docker.com/

2、注册一个账号,并确定这个账号可以登录

3、登录到 Docker Hub

bash
$ docker login --help
Usage:  docker login [OPTIONS] [SERVER]

Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.

Options:
  -p, --password string   Password
      --password-stdin    Take the password from stdin
  -u, --username string   Username

$ docker login -u 你的用户名 -p 你的密码
#也可以 docker login -u 你的用户名 直接回车,然后在下面提示中输入密码!

4、提交:push 镜像

bash
$ docker push 镜像名或者镜像id
img

5、我们发现报错了,解决办法:

bash
# 会发现push不上去,因为如果没有前缀的话默认是push到 官方的library(我们需要修改仓库位置)
# 解决方法:
# 第一种:在build镜像的时候就添加你的dockerhub账号,这样直接push就可以放到自己的仓库了
$ docker build -t 1558637209/mytomcat:0.1 .

# 第二种:使用docker tag命令:指定远程标签(即到自己的仓库),然后再次push
#格式为:tag 镜像名 dockerhub用户名/镜像名:版本号
$ docker tag mytomcat 1558637209/mytomcat:0.1 #然后再次push
$ docker push 1558637209/mytomcat:0.1

6、docker logout 命令可以退出登录

6.8.2 发布到 阿里云镜像服务上

1.登录阿里云

2.找到容器镜像服务

3.创建一个命名空间

3.创建容器镜像

看官网 很详细https://cr.console.aliyun.com/repository/

bash
$ docker login --username=zchengx registry.cn-shenzhen.aliyuncs.com
$ docker tag [ImageId] registry.cn-shenzhen.aliyuncs.com/dsadxzc/cheng:[镜像版本号]

# 修改id 和 版本
$ docker tag a5ef1f32aaae registry.cn-shenzhen.aliyuncs.com/dsadxzc/cheng:1.0
# 修改版本
$ docker push registry.cn-shenzhen.aliyuncs.com/dsadxzc/cheng:[镜像版本号]

总结:我们发布 docker 镜像到了远程仓库,那么别人就可以直接 docker pull 拉取镜像到虚拟机上,然后直接运行镜像就可以部署访问我们的项目了!

6.9 小结

docker 镜像的操作全流程:

docker save 是备份镜像

img

7.Docker 网络

注:Docker 网络是容器编排,集群部署的基础!

7.1 理解 Docker 0

学习之前先清空下前面的 docker 镜像、容器

bash
# 删除全部容器
$ docker rm -f $(docker ps -aq)

# 删除全部镜像
$ docker rmi -f $(docker images -aq)

测试

linux 中获取当前 ip 地址:

bash
$ ip addr

img

linux 主机的 docker0 的地址为:172.168.0.1

三个网络

问题:docker 是如果处理容器间的网络访问的?

比如说我们这个容器里面有一个 tomcat,里面有一个项目,这个项目需要去访问另外一个容器上面的 mysql

img

测试

bash
# 测试  运行一个tomcat
$ docker run -d -P --name tomcat01 tomcat #-p是指定端口映射的,大写小写都可以,如果后面没有写端口,就说明里面的端口可以任意暴露

# 查看容器的内部网络地址(直接在后面追加一个ip addr命令即可)
$ docker exec -it 容器id ip addr
# 运行结果:
# 发现容器启动的时候会得到一个 eth0@if91 的 ip地址,这其实是 docker 分配的!
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
    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
261: eth0@if91: <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


# 思考:在docker分配ip地址的基础上
# linux能不能ping通容器内部? 可以!
# 容器内部可以ping通外界吗? 可以!
# 我们在宿主机上ping容器:发现成功了
$ 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.069 ms
64 bytes from 172.18.0.2: icmp_seq=2 ttl=64 time=0.074 ms

linux 主机的 docker0 的 ip 地址为:172.168.0.1,而容器被分配的 ip 地址为 172.168.0.2,他们都在同一个网段里面,0.1 和 0.2 是网关,所以他们一定可以

ping 通!

原理

我们每启动一个 docker 容器,docker 就会给 docker 容器分配一个 ip,我们只要安装了 docker,就会有一个 docker0 网卡

这个网卡采用桥接模式,使用的技术是 evth-pair 技术!

相关介绍:https://www.cnblogs.com/bakari/p/10613710.html

1、再次在宿主机中测试 ip addr

发现多了一个网络

img

2 、再启动一个容器测试,发现又多了一个网络

img

注:veth-pair 技术

那么也就是说每创建一个容器,容器内会有一个 docker 分配的 ip 地址,宿主机上也会有一个相对应的 ip 地址

所以我们发现这个容器带来的网卡,都是一对一对的

veth-pair 就是一对的虚拟设备接口,他们都是成对出现的,一端连着协议,一端彼此相连

正因为有这个特性,我们得以实现容器内外的访问,进而实现容器之间的访问:拿 veth-pair 充当一个桥梁,这个技术用于连接各种虚拟网络设备

例如:OpenStac,Docker 容器之间的连接,OVS 的连接,都是使用的 evth-pair 技术

3、我们来测试下 tomcat01 和 tomcat02 是否可以 ping 通

bash
# 获取tomcat01的ip 172.17.0.2
$ docker-tomcat docker exec -it tomcat01 ip addr
550: eth0@if551: <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

# 让tomcat02 ping tomcat01
$ docker-tomcat docker exec -it tomcat02 ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.098 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.071 ms

结论:容器和容器之间是可以互相 ping 通的

7.2 网络模型

img

结论:

tomcat01 和 tomcat02 公用一个路由器:docker0,这个路由器上的两个接口分别使用 veth-pair 桥接技术连接两个容器,而这个 docker0 本身又作为一个桥梁。

所有的容器在不指定网络的情况下,都是使用 docker0 路由的,docker 就会给我们的容器分配一个默认的可用 ip,最多分配 65535 个!

小结

Docker 使用的是 Linux 的桥接(桥接过来的网卡),宿主机中有一个 Docker 容器的网桥:即 docker0

img

Docker 中所有的网络接口都是虚拟的,因为虚拟接口的转发效率高(例如内网传递文件)!

但是只要容器删除了,对应的网桥,即一对网络就没了!

思考一个场景:

我们编写了一个微服务,database url=ip: 项目不重启,数据库 ip 换了,我们希望可以处理这个问题,希望可以通过名字来进行访问容器,如何做到?

使用–-link!

测试:使用容器名字进行 ping

bash
$ docker exec -it tomcat02 ping tomca01   # ping不通
ping: tomca01: Name or service not known

#用--link进行连接:
# 运行一个tomcat03 --link tomcat02 :相当于在tomcat03的hosts配置中添加了映射,这样ping名字就可以ping到ip地址!
$ docker run -d -P --name tomcat03 --link tomcat02 tomcat
5f9331566980a9e92bc54681caaac14e9fc993f14ad13d98534026c08c0a9aef

# 3ping2
# 用tomcat03 ping tomcat02 可以ping通
$ docker exec -it tomcat03 ping tomcat02
PING tomcat02 (172.17.0.3) 56(84) bytes of data.
64 bytes from tomcat02 (172.17.0.3): icmp_seq=1 ttl=64 time=0.115 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.080 ms

# 2ping3
# 用tomcat02 ping tomcat03 ping不通!!

探究原因:

1.docker network ls 查看所有网络(包括网络 id)

2.docker network inspect 网络 id 查看网络配置

我们发现三个容器的网段相同

img

3.docker inspect tomcat03 查看容器配置

img

4.查看 tomcat03 里面的/etc/hosts 文件发现有 tomcat02 的配置

img

结论:所以--link 的本质就是在 hosts 配置文件中添加映射

但是现在使用 Docker 已经不建议使用–-link 了!

我们希望自定义网络,不使用 docker0!(docker0 是官方提供的!)

docker0 的问题:不支持使用容器名来进行连接访问!

7.4 自定义网络

bash
docker network
connect     -- Connect a container to a network
create      -- Creates a new network with a name specified by the
disconnect  -- Disconnects a container from a network
inspect     -- Displays detailed information on a network
ls          -- Lists all the networks created by the user
prune       -- Remove all unused networks
rm          -- Deletes one or more networks

查看所有的 docker 网络信息

img

NAME 是网络模式!

四大网络模式

bridge :桥接 docker(docker 默认,自己创建也是用 bridge 模式)例如:在一个网段里面有 0.1,0.2,0.3,我们想让 0.1 访问 0.3,可以把 0.2 作为桥!

none :不配置网络,一般不用

host :和宿主机共享网络

container :容器网络连通(用得少!局限性很大)

测试

1.自定义一个网络

bash
# 我们直接启动的命令其实在后面隐藏了一句--net bridge,而这个的意思就是使用docker0
# 这个bridge就是docker0
$ docker run -d -P --name tomcat01 tomcat
#等价于 => docker run -d -P --name tomcat01 --net bridge tomcat

# docker0,特点:默认,域名不能访问。 --link可以打通连接,但是很麻烦!

# 其实我们可以 自定义一个网络:相当于就是自定义一个路由器!
# --driver指定网络模式,--subnet规定子网,/16说明最多65535个子网,--gateway规定网关.0.1
$ docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

img

2.查看配置信息

bash
$ docker network inspect mynet #查看mynet路由器的配置信息

img

3.启动两个 tomcat 容器,再次查看网络情况:使用--net 指定网络路由器!

img

4.再次查看 mynet 的路由器配置信息:发现多了两个容器

img

5.进一步测试,发现在自定义的网络下,容器可以直接用容器名互相 ping 通,不用使用–-link 连接!

img

结论:docker 帮我们维护好了我们自定义的网络对应的关系,推荐平时这样使用网络!

好处

比如我现在有两个集群:

redis 集群 -不同的集群使用不同的网络(每个集群都有自己的子网,自己的网段),保证集群是安全和健康的

mysql 集群 -不同的集群使用不同的网络,保证集群是安全和健康的

img

问题:既然网段不一样,互相隔离,那么如何连通呢?

7.5 网络连通

我们有两个路由器,应用于上面的项目可以互相 ping 通吗?

image-20220720025037589

测试

docker network connect 网络名 容器名 命令连通两个网络!

本质上就是把一个容器加到一个网络里面,但不脱离原来的网络,这时这一个容器会有两个 ip!

imgimg

1.再启动两个使用 docker0 的 tomcat

bash
# 测试两个不同的网络连通  再启动两个tomcat 使用默认网络,即docker0
$ docker run -d -P --name tomcat01 tomcat
$ docker run -d -P --name tomcat02 tomcat
# 此时ping不通

用 mynet 上的 tomcat-net-01 去 ping 默认 docker0 上的 tomcat01:

img

2.连通网络:

bash
# 要将tomcat01 连通 tomcat—net-01 ,这个连通的意思 就是将 tomcat01加到 mynet网络
# 一个容器拥有两个ip(tomcat01)
bash
$ docker network connect mynet tomcat01

img

bash
# tomcat01可以和mynet网络内的容器连通了 ,也就是说tomcat01 已经可以和 tomcat-01-net ping通了
# 但是tomcat02依旧和mynet网络不连通

结论:假设容器要跨网络操作容器,就需要使用 docker network connect 连通那个网络!

7.6 实战:部署 Redis 集群

我们要部署的集群的模型:三主三从(注意这里一个容器就是一个节点,之前是一台机器才是一个节点)

也就是每个主机都有一个备用机,如果主机宕机,备用机就顶替

需要 6 个容器

img

步骤

bash
# 1.创建Redis网络的网卡
$ docker network create redis --subnet 172.38.0.0/16

# 2.通过Shell脚本创建6个redis配置:这段代码直接复制在命令行即可,会自动在/mydata/redis/下面创建6个节点,每个节点都有redis.conf文件
$ 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

# 3.继续通过Shell脚本运行6个redis
$ for port in $(seq 1 6);\
docker run -p 637${port}:6379 -p 1667${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 #使用我们自定义的redis网络运行

# 4.进入redis容器
$ docker exec -it redis-1 /bin/sh # redis默认是没有bash的,所以我们用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 # --cluster create创建高可用集群、三主三从,--cluster-replicas 1切片为 1

img

docker 搭建 redis 集群完成!

bash
redis-cli -c #进入集群命令行
#在进入后的命令行输入
cluster info #可以看到集群信息
cluster nodes #可以看到节点信息:三主三从
set a b #会随机有一台主机帮我们完成这个
#我们把这个主机关掉,即停止这个容器
get a
#发现依然可以得到值,因为集群的高可用,从机代替了主机!

img

我们使用 docker 之后,所有的技术都会慢慢变得简单起来!

8.SpringBoot 微服务打包 Docker 镜像发布

1、构建 SpringBoot 项目

2、package 打包应用为 jar 包

3、在 idea 中编写 dockerfile 文件

就叫做 Dockerfile

java
FROM java:11 //环境为jdk11
// 有时候写:FROM openjdk:11
COPY *.jar /app.jar //将当前目录下的所有jar包,拷贝为app.jar
CMD ["--server.port=8080"] //设置jar运行端口为8080
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"] //执行java-jar命令运行jar包

4、上传项目

在 linux 中构建/home/idea 目录

用 xftp 上传 jar 包和 DockerFile 文件到服务器

5、利用 dockerfile 构建镜像

格式:docker build -f dockerfile 名 -t 镜像名:版本号 .

注:也可以是 docker build -t 镜像名:版本号 .

因为本身名字就叫做 Dockerfile,可以自动识别!

bash
$ docker build -t hao666:01 .

开始执行,自动构建镜像

6、运行镜像,启动容器

--name hao-springboot-web 是指定的容器名!

bash
$ docker run -d -p 9000:8080 --name hao-springboot-web hao666:01
# docker run -d -p 9000:8888 --name hao-springboot-web hao666:01

7、访问测试

bash
$ curl localhost:9000

成功!

8、发布镜像

好处

1、我们实现了对于 springboot 项目的长期端口部署,之前我们只能让 springboot 项目暂存,运行 jar 包退出之后这个项目就访问不了了,但是用了 docker

之后可以让项目长期存于容器中,容器后台运行,很方便!

2、我们使用了 Docker 之后,给别人交付就是一个镜像即可(镜像里面包括了项目+环境)!别人直接 pull 镜像,然后创建容器运行(不需要自己下载环境),就

快速部署了我们的项目了!——> 当然前提是别人的电脑上要有 docker!

IDEA 整合 Docker

教程博客:https://blog.csdn.net/fly910905/article/details/113646329?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165825981416782391836016%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165825981416782391836016&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-113646329-null-null.142^v32^experiment_2_v1,185^v2^control&utm_term=idea怎么连接docker&spm=1018.2226.3001.4187

image-20220720042057584

Docker 整合 Docker

Docker Compose

Docker Swarm

CI/CD 之 Jenkins