Docker 从入门到入土

1、Docker简介

1.1 虚拟化技术

介绍Docker之前有必要了解一下虚拟化技术,其实Docker的出现也是虚拟机技术发展的一个里程碑。随着企业业务量的不断提升,需要横向的扩展多个计算机实例(节点)来满足业务,比如做节点的主备,横向扩展负载均衡的计算节点等,简单地说就是需要多台计算节点(主机)。其实扩展计算节点,最简单的方法就是搭建多台物理机,但这样做成本过高,因此有了建立在物理机之上的虚拟机的诞生,比如VMWare,这就是对宿主机物理资源的一种虚拟化,也就是这个小节要介绍的内容。

虚拟化的定义是什么呢?虚拟化是指在一台宿主机的基础上搭建多个虚拟的运行环境,将宿主机(物理机)的硬件资源虚拟化成运行环境所需要的的资源,比如操作系统、网卡等。虚拟化技术我认为大致分为两大类(按照发展时间顺序):Hypervisor容器

1.1.1 Hypervisor

Hypervisor是指用软件仿真一台电脑出来,在这台虚拟出来的电脑上安装操作系统,配置网络,安装运行软件等等,宿主机有一套操作系统,每个虚拟机有各自的操作系统,且允许宿主机和虚拟机的操作系统不同(windows和linux这种级别的不同),如图所示:

img

常见的Hypervisor技术有KVM、VMWare等,还有H3C的CAS。说到这里就不得不提到大名鼎鼎的openstack了,openstack是一款基于python的云管理平台,其功能之一就是管理底层不同Hypervisor技术虚拟化出来的资源,并对外提供统一的API接口来访问并使用这些虚拟化资源,比如用KVM虚拟化了一台主机,用VMWare虚拟化出来了一台主机,外部使用者仅需要使用openstack提供的Nova的api就可以访问这两台主机,而不需要关心底层到底是如何虚拟化的。

这里再引入一个云计算领域的名词:IaaS(Infrastructure as a Service),即基础设施服务。云计算领域的业务自底向上依次为IaaS、PaaS、SaaS,这里简单说一下跟虚拟化有关的IaaS的概念。IaaS作为云厂商的一种服务对外提供,这种服务是云厂商将计算、存储和网络这三大物理资源虚拟化后对外提供的一种能力或是服务,比如用户需要一台虚拟机,可以直接使用公有云厂商或者私有云厂商的接口去创建一台虚拟机,至于虚拟机是如何创建的,在哪创建的都不需要用户关心,但用户可以通过可视化的界面和api去管理自己创建的资源,而不是用户自己费时费力的搭建一台宿主机,再在宿主机上搞几台虚拟机,还要不断的去维护。

1.1.2 容器

上面介绍的Hypervisor技术虚拟化物理资源产生的虚拟机有以下三个问题:

  • 虚拟机里面的操作系统本身会占用相当一部分资源(一般几个G),且这些操作系统基本都是一样的,是存在复用的空间的;

  • 虚拟化出主机前要提前给虚拟机分配内存、系统盘这些资源,虚拟化后即使虚拟机不在运行状态也会占用宿主机这部分物理资源,存在资源利用率不高的情况;

  • 虚拟机启动速度不快。

正因为Hypervisor技术的弊端,推动着虚拟化技术不断地向前探索,并诞生了虚拟化技术的有一个里程碑意义的技术:容器(container)。相比于虚拟机是属于操作系统级别的,容器是属于进程级别的,即多个容器共用宿主机的操作系统,且每个容器之间都是相互隔离的,那容器是如何做到相互之前隔离的呢?主要有文件系统隔离和资源隔离这两大隔离:

  • 文件系统隔离

    • 每个容器都具有独立的文件系统,单个容器内对文件系统进行增删改查不会影响到其他容器;
    • 参考 Linux 下的 chroot 命令,可以将子目录变为根目录。
  • 资源隔离

    • 利用 namespace 隔离进程之间的相互可见及通信;
    • 利用 Cgroup 限制资源使用率,设置其能够使用的 CPU 以及内存大小。

容器的示意图如图所示:

img

这里对Hypervisor技术(或者叫虚拟机)和容器技术进行比较:img

容器技术的实现也有若干种,比如大名鼎鼎的Docker,但容器技术并不等于Docker,Docker是****创建容器的工具,是容器的引擎,而不是容器技术本身,另外容器技术除了Docker还有CoreOS rkt、Mesos、lxc 等,但Docker目前占有了容器市场的83%份额。说到容器技术就有必要提一下当下大火的Kubernetes****(K8S),K8S是谷歌开源的项目,起源于谷歌内部积累了10几年的大规模服务器调度系统,是用来管理和调度容器的,K8S基于Docker(以及其他容器化工具),实现在分布式集群上通过一套API对多个容器进行便捷的管理、服务发现、路由转发等等,可能以后我们输命令都不是linux命令了,而是K8S的命令了。

说到K8S就有必要介绍一下云原生的概念了,云原生简单地说就是用户的软件开发的整个生命周期都是在云上进行的,具体来讲,在软件开发过程中,最直接的用户需要开发、测试、生产环境,即需要虚拟机,这时用户可以用IaaS里的计算服务获取计算资源,再比如用户比如用户需要将数据存储下来,并在需要的时候读取上来,这就需要IaaS里的存储服务存储数据;用户在开发过程中,可能需要mysql或者redis这样的中间件,在CI/CD时需要构建自己的流水线来推动自己的devops建设,在将自己的传统业务转型成微服务化时,可以使用PaaS平台的能力;在软件通过测试和发布后,部署运维阶段可以使用K8s和Docker技术管理自己的应用...整个开发周期都是在云上进行的。CNCF(Cloud-Native Compute Foundation,云原生计算基金会)定义的云原生的三大特征为:

  • 容器化封装:以容器为基础,提高整体开发水平,形成代码和组件重用,并作为应用程序部署的独立单元;

  • 动态和自动化管理:通过集中式的编排调度系统来对资源进行动态的管理和调度,即K8S。

  • 面向微服务:明确服务间的依赖,互相解耦。

1.1.3 虚拟化领域有关概念和名词

这里总结了上面介绍内容中有关虚拟化的名词和概念,如下表:

名词 概念
虚拟化 在一台宿主机的基础上搭建多个虚拟的运行环境,将宿主机(物理机)的硬件资源虚拟化成运行环境所需要的的资源,比如操作系统、网卡等,分为Hypervisor和容器两类。
Hypervisor 用软件仿真一台电脑出来,在这台虚拟出来的电脑上安装操作系统,配置网络,安装运行软件等等,宿主机有一套操作系统,每个虚拟机有各自的操作系统,常见的Hypervisor技术有KVM、VMWare等。
openstack openstack是一款基于python的云管理平台,用户可以通过API访问管理底层的虚拟化资源(底层兼容多种不同的虚拟化方式),并有着自己的一套认证鉴权体系。
IaaS 云计算领域业务划分的三类之一,也是最底层的,即基础设施服务,包括对计算、存储和网络的三大虚拟化。
容器 虚拟化方式的一种,进程级别的虚拟化方式,容器间共用底层宿主机的操作系统。
docker Docker是创建容器的工具,是应用容器的引擎。
K8s K8S通过编排调度对容器进行管理和调度。
云原生 软件开发的一种模式,软件的整个声明周期都是在云上进行的,并有着容器、K8S、devops和微服务化的特征。

1.2 Docker基本名词和概念

1.2.1 Docker架构

Docker也是经典的client-server架构,如下如图所示:

img

如上图所示:我们使用Docker时输入的Docker开头的命令都是在client端输入,client端向Docker服务器或者守护进程发出请求,服务器或者守护进程将完成所有工作并返回结果,Docker提供了一个命令行工具以及一整套 RESTful API可以在客户端使用。可以在同一台宿主机上运行 Docker 守护进程和客户端,也可以从本地的Docker 客户端连接到运行在另一台宿主机上的远程 Docker守护进程。从上图也可以看出,容器(container)是运行在Docker服务端的,但一般都会把Docker服务端和客户端安装在一个节点上,就仿佛容器就是跟客户端是一个环境,这一点要区分开。

下面介绍Docker的三个重要的概念:

  • 镜像(image)

  • 容器(container)

  • 仓库(Registry)

1.2.2 镜像

Docker 镜像就是一个 Linux 的文件系统(Root FileSystem),这个文件系统里面包含可以运行在 Linux 内核的程序、运行程序所依赖的库文件、环境变量和配置文件等,可以简单理解成对当前程序和运行的环境的一个快照(snapshot)。关于Docker的分层镜像文件系统后面单独详细地讲。

1.2.3 容器

容器是应用程序运行的环境,镜像和容器的关系就好比OOP里的类和对象的关系,容器可以被创建、启动、停止、删除、暂停等 ,容器的实质是进程,但与直接在宿主机上执行的进程不同,容器进程运行在属于自己的独立的命名空间。

1.2.4 仓库

Docker用仓库来存放各种镜像,Registry分为公共和私有两种,Docker公司运营公共的Registry叫做 Docker Hub,每个公司可以搭建自己的私有仓库,这一点就跟Maven的公共仓库和私服一样。通常一个仓库会包含同一个软件不同版本的镜像,而标签对应该软件的各个版本,我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像,如果标签缺省,将以 latest 作为默认标签。

1.3 Docker流程

img

2、Docker安装

所有有关Docker的内容在Docker的官方文档写的很详细,虽然是英文的。下面的教程是基于Centos7的。

(1)卸载环境上旧的Docker

 sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

(2)安装需要的安装包

sudo yum install -y yum-utils

(3)设置镜像仓库(使用阿里云镜像加速)

sudo yum-config-manager \
    --add-repo \
    http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

(4)安装io,否则报错

dnf install https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.6-3.3.el7.x86_64.rpm

(5)安装最新版的docker

sudo yum install docker-ce docker-ce-cli containerd.io

如果这一步报错,尝试第四步里安装更高版本的io,比如:

dnf install https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.4.3-3.1.el7.x86_64.rpm

(6)启动docker

sudo systemctl start docker

(7)测试,运行hello world镜像的容器

sudo docker run hello-world

(8)阿里云镜像加速

登录阿里云容器镜像加速页面:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors

img

sudo mkdir -p /etc/docker

sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://1fendj48.mirror.aliyuncs.com"]
}
EOF

sudo systemctl daemon-reload

sudo systemctl restart docker

卸载Docker的步骤:

(1)卸载依赖

sudo yum remove docker-ce docker-ce-cli containerd.io

(2)删除目录

sudo rm -rf /var/lib/docker

此外,针对阿里云ECS的Alibaba Cloud Linux系统,Docker安装请参考:https://helpcdn.aliyun.com/document_detail/51853.html

3、基本命令

3.1 镜像基本命令

3.1.1 帮助命令:

docker 命令 --help

3.1.2 显示docker版本信息

docker version

3.1.3 显示docker系统信息

docker info

3.1.4 查看主机所有的镜像

docker images [可选项]

结果:

img

说明:

  • REPOSITORY :镜像的仓库源

  • TAG:镜像标签

  • IMAGE ID :镜像Id

  • CREATED:镜像创建时间

  • SIZE:镜像大小

可选项:

  • -a, -all:列出所有镜像

  • -q, --quiet:只显示镜像的id

  • -aq,显示所有镜像的id

这里单独说一下上图中的REPOSITORY,Docker是把一个系列的镜像放到一个仓库(就是这个REPOSITORY),比如把mysql的不同TAG(版本)的镜像都放到REPOSITORY为mysql的仓库中,因此docker有两种方式唯一对应一个镜像:

  • REPOSITORY:TAG
  • IMAGE ID

3.1.5 搜索镜像

docker search [可选项] 组件名称

可选项:

  • -f, --filter:过滤条件,比如 docker search --filter=STARTS=3000 mysql,表示搜索STARS大于3000的mysql镜像。

3.1.6 下载镜像

docker pull 组件名称   				# 省略镜像版本默认是下载最新版本:latest
docker pull 组件名称: 版本号   # 指定版本号下载,这个版本号就是下载后镜像的TAG

3.1.7 删除镜像

docker rmi -f 镜像Id											# 删除指定镜像Id的镜像
docker rmi -f $(docker images -aq)        # 删除所有镜像
docker rmi 镜像名:版本号                   # 当有多个镜像的id一样时,可以通过指定版本号的镜像名删除

3.1.8 构建镜像

前提是写了一个镜像的DockerFile,

docker build  -t ImageName:TagName dir

可选项

  • -t : 给镜像加一个Tag

  • ImageName : 给镜像起的名称

  • TagName : 给镜像的Tag名

  • Dir :Dockerfile所在目录

举例:

docker build -t myimg:0.1 .

说明:构建了一个镜像名为myimg,版本号为0.1的镜像,注意0.1后面有个空格和点,点表示当前目录,即上面的dir参数,此时Dockerfile必须在执行构建镜像命令的目录下。

3.1.9 上传镜像

  • 将本地的镜像上传到镜像仓库,要先登陆到镜像仓库;
  • NAME最好是路径名/镜像名,最好带上版本号,加以区分。
docker push [OPTIONS] NAME[:TAG]

3.1.10 给镜像打标签

docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]

比如我现在有一个 centos 镜像:

[root@localhost ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              1e1148e4cc2c        2 weeks ago         202MB

我对 centos 进行开发,开发了第一个版本,我就可以对这个版本打标签,打完标签后会生成新的镜像:

[root@localhost ~]$ docker tag centos centos:v1
[root@localhost ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              1e1148e4cc2c        2 weeks ago         202MB
centos              v1                  1e1148e4cc2c        2 weeks ago         202MB

我继续对 centos 进行开发,开发了第二个版本,继续打标签:

[root@localhost ~]$ docker tag centos centos:v2
[root@localhost ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
centos              latest              1e1148e4cc2c        2 weeks ago         202MB
centos              v1                  1e1148e4cc2c        2 weeks ago         202MB
centos              v2                  1e1148e4cc2c        2 weeks ago         202MB

以此类推,每开发一个版本打一个标签,如果以后我想回滚版本,就可以使用指定标签的镜像来创建容器:

root@localhost ~]$ docker run -itd centos:v1

注意:当你没有对镜像做任何写操作,而仅仅是在docker tag时改了个镜像名,那么docker tag生成的镜像会和之前的镜像的id一样,只是镜像名不同。

3.1.11 从容器中创建一个新镜像

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]

OPTIONS说明:

  • -a :提交的镜像作者;

  • -c :使用Dockerfile指令来创建镜像;

  • -m :提交时的说明文字;

  • -p :在commit时,将容器暂停。

举例:将容器a404c6c174a2 保存为新的镜像,并添加提交人信息和说明信息:

docker commit -a "runoob.com" -m "my apache" a404c6c174a2  mymysql:v1 

3.2 容器基本命令

3.2.1 新建容器并启动

docker run [可选项] 镜像名称

可选项:

  • --name="容器",用来区分容器

  • -d,后台方式运行

  • -it:使用交互方式运行,进入容器内部查看内容,也是进入容器使用最多的命令,比如运行centos镜像:docker run -it centos /bin/bash

  • -p:指定容器的端口,比如-p 主机端口:容器端口

  • -P:随机指定端口

  • -net:指定容器与哪个网络相连

img

这里对-p可选性做进一步说明:

首先说一下docker容器的端口和宿主机端口的映射,需要对docker容器的端口和宿主机端口做映射,才能通过ip:宿主机port访问到对应的容器,这个映射是在创建并启动docker容器时指定的,关于映射需要注意:

  • 宿主机的一个端口只能映射到容器内部的某一个端口上,比如:8080->80之后,就不能8080->81;
  • 容器内部的某个端口可以被宿主机的多个端口映射,比如:8080->80,8090->80,8099->80。

启动容器时,选择一个端口映射到容器内部开放端口上:

[root@docker-test ~]# docker run -ti -d --name my-nginx -p 8088:80 docker.io/nginx
2218c7d88ccc917fd0aa0ec24e6d81667eb588f491d3730deb09289dcf6b8125
[root@docker-test ~]# docker run -ti -d --name my-nginx2 -P docker.io/nginx
589237ceec9d5d1de045a5395c0d4b519acf54e8c09afb07af49de1b06d71059
[root@docker-test ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED              STATUS              PORTS                   NAMES
589237ceec9d        docker.io/nginx     "nginx -g 'daemon ..."   6 seconds ago        Up 5 seconds        0.0.0.0:32770->80/tcp   my-nginx2
2218c7d88ccc        docker.io/nginx     "nginx -g 'daemon ..."   About a minute ago   Up About a minute   0.0.0.0:8088->80/tcp    my-nginx
 
由上面可知:
容器my-nginx启动时使用了-p,选择宿主机具体的8088端口映射到容器内部的80端口上了,访问http://localhost/8088即可
容器my-nginx2启动时使用了-P,选择宿主机的一个随机端口映射到容器内部的80端口上了,这里随机端口是32770,访问http://localhost/32770即可

启动创建时,绑定外部的ip和端口(宿主机ip是192.168.10.214):

[root@docker-test ~]# docker run -ti -d --name my-nginx3 -p 127.0.0.1:8888:80 docker.io/nginx 
debca5ec7dbb770ca307b06309b0e24b81b6bf689cb11474ec1ba187f4d7802c
[root@docker-test ~]# docker run -ti -d --name my-nginx4 -p 192.168.10.214:9999:80 docker.io/nginx              
ba72a93196f7e55020105b90a51d2203f9cc4d09882e7848ff72f9c43d81852a
[root@docker-test ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                         NAMES
ba72a93196f7        docker.io/nginx     "nginx -g 'daemon ..."   2 seconds ago       Up 1 second         192.168.10.214:9999->80/tcp   my-nginx4
debca5ec7dbb        docker.io/nginx     "nginx -g 'daemon ..."   3 minutes ago       Up 3 minutes        127.0.0.1:8888->80/tcp        my-nginx3
 
由上面可知:
容器my-nginx3绑定的宿主机外部ip是127.0.0.1,端口是8888,则访问http://127.0.0.1:8888或http://localhost:8888都可以,访问http://192.168.10.214:8888就会拒绝!
容器my-nginx4绑定的宿主机外部ip是192.168.10.214,端口是9999,则访问http://192.168.10.214:9999就可以,访问http://127.0.0.1:9999或http://localhost:9999就会拒绝!

3.2.2 从容器中退回宿主机

exit			     # 容器停止并退出
Ctrl + p + q   # 容器不停止退出

3.2.3 显示当前正在运行的容器

docker ps      		# 打印当前正在运行的容器
docker ps -a   		# 打印历史运行过的容器
docker ps -n=10   # 打印前n条容器记录,可配合-a使用
docker ps -q      # 仅显示容器的id,可配合-aq使用

3.2.4 删除容器

docker rm 容器id                   # 删除指定容器,不能删除正在运行的容器
docker rm -f 容器id								 # 删除指定容器,强制删除
docker rm -f $(docker ps -aq)      # 删除所有容器

3.2.5 启停容器

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

3.3 其他命令

3.3.1 查看日志

docker logs -tf 容器id   # 查看指定容器的日志,-f是可以看到即时日志
docker logs -f -t --tail 10 容器id   # 查看指定容器的日志,仅显示10条日志

3.3.2 查看容器中进程的信息

docker top 容器id

img

3.3.3 查看容器的元数据

docker inspect 容器id

3.3.4 进入当前正在运行的容器

docker exec -it 容器id /bin/bash
docke attach 容器id /bin/bash

区别:

  • docker exec,进入容器后开启一个新的终端,可以在里面操作
  • docke attach,进入容器正在执行的终端

docker exec -it后面还可以追加linux命令,举例:

# 进入容器tomcat02并做ping命令
docker exec -it tomacat02 ping 192.108.1.1

docker run和docker exec的区别:

  • docker run :根据镜像创建一个容器并运行一个命令,操作的对象是镜像
  • docker exec :在运行的容器中执行命令,操作的对象是容器

3.3.5 将容器内的文件拷贝到容器外宿主机上

docker ps 容器id:容器内路径 宿主机路径

举例:docker cp 09fce574695f:/home/jerry.java /home

docker cp 是一个手动拷贝的过程,后面我们使用卷的技术,可以实现自动同步。

3.4 网络命令

3.4.1 查看Docker网络

docker network ls

img

3.4.2 查看Docker网络详情

docker network inspect docker网络ID

其中docker网络ID可以通过docker network ls查看。

举例:查看本机docker0网络的信息:

docker network inspect 7f12423b5112

部分截图如下:

img

img

上图也是当前网络被哪些容器使用的图。

使用docker inspect 容器id查看容器的详情,也能查询到容器的网络信息,如下:

[root@iZ2vcf0atudng4otf06u38Z ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                     NAMES
e493a6ee1baa        tomcat              "catalina.sh run"   13 minutes ago      Up 13 minutes       0.0.0.0:32770->8080/tcp   tomcat03
6fe98b6b9c81        tomcat              "catalina.sh run"   23 hours ago        Up 23 hours         0.0.0.0:32769->8080/tcp   tomcat02
5296f3a9910b        tomcat              "catalina.sh run"   23 hours ago        Up 23 hours         0.0.0.0:32768->8080/tcp   tomcat01
[root@iZ2vcf0atudng4otf06u38Z ~]# docker inspect 6fe98b6b9c81

网络信息部分的截图如下:

img

3.4.3 删除Docker网络

支持一次删除一个或多个Docker网络。

docker network rm 网络名称1, 网络名称2...

3.4.4 创建Docker网络

docker network create --driver 网络模式 --subnet 子网范围 --gateway 网关地址 自定义网络的名称

举例:

# 创建一个如下网络:
# 网络模式是桥接模式
# 网络名词是mynet
# 子网信息是:192.168.0.0/16
# 网关地址是:192.168.0.1

docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

创建好后查看docker网络,可以看到新建的mynet网络已经出现:

[root@iZ2vcf0atudng4otf06u38Z ~]# docke network ls
-bash: docke: command not found
[root@iZ2vcf0atudng4otf06u38Z ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
7f12423b5112        bridge              bridge              local
a70143b200b4        host                host                local
efaf028a11ad        mynet               bridge              local
7cd362f44529        none                null                local

3.4.5 连通Docker网络

意思是连通宿主机内不同网段的Docker网络,示意图如下:

img

上图中,mynet01和mynet02不是一个网段的,tomcat01和tomcat02肯定无法ping通tomcat03和tomcat04容器,反之亦然。 可以使用docker network connect 将一个容器放置在某个网络下:

docker network connect 网络名 容器名

使用docker network inspect 网络名 可以查看网络下对应哪些容器。

举例:

docker network connect mynet02 tomcat01

将容器tomcat01放置在mynet02网络下,此时再将tomcat01与tomcat03和tomcat04互相ping是可以ping通的,因为这三个容器都是在一个网络下,而tomcat02仍然不可以,除非将tomcat02也放置在mynet02下。

4、Docker镜像分层文件系统

4.1 基于联合文件系统的分层镜像系统

Docker的镜像分层系统是基于联合文件系统(UnionFs)实现的。

什么是联合文件系统?联合文件系统是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下,表现形式就是:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。

img

上图是个很经典的图,展示了Docker分层的镜像系统。一般最底层是个bootfs,这个就对应linux的引导层;在bootfs上面的一层一般是rootfs,包含的就是典型 Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件,rootfs可以理解为各种不同的操作系统发行版,比如:Ubuntu,、CentOS等;rootfs上面就是我们具体的镜像层了,一般在底层的是基础镜像层(Base Image),基础镜像层可能是一层,也可能是基层;基础镜像层上面就是各自的镜像层了,这个镜像层也可以是一层,也可以是多层;下面的镜像层是上面镜像层的父镜像。每安装一个软件,就在现有镜像的基础上增加一层。这样设计的最大好处就是不同的镜像可以共享基础镜像层,Docker Host 只需在磁盘上保存一份 base 镜像(因为镜像的ID唯一),同时内存中也只需加载一份 base 镜像,就可以为所有容器服务了。

4.2 容器层和镜像层

当从一个镜像启动容器时,Docker会在该镜像的最顶层加载一个读写文件系统,我们在Docker中运行的程序就是在这个读写层中执行的,这个读写层通常被称作容器层,容器层之下的都叫镜像层。所有对容器的改动 ,即添加、删除、修改都只会发生在容器层中,只有容器层是可写的,容器层下面的所有镜像层都是只读的。如图所示:

img

当对容器层进行修改时,具体有以下几种场景:

  • 添加文件:在容器中创建文件时,新文件被添加到容器层中;

  • 读取文件:在容器中读取某个文件时,Docker会从上往下依次在各镜像层中查找此文件,一旦找到,打开并读入内存;

  • 修改文件:在容器中修改已存在的文件时,Docker会从上往下依次在各镜像层中查找此文件,一旦找到,立即将其拷贝一份到容器层,然后修改之;

  • 删除文件:在容器中删除文件时,Docker也是从上往下依次在镜像层中查找此文件。一旦找到,会在容器层中记录下此删除操作。

只有当需要修改时才复制一份数据,这种特性被称作Copy-on-Write

5、Portainer可视化面板

Portainer跟Rancher一样,也是容器的一个可视化管理平台。Portainer的单机安装环境十分简单,如下:

# 搜索镜像
docker search portainer/portainer
# 拉取镜像
docker pull portainer/portainer
# 运行镜像
docker run -d -p 9000:9000 -v /root/portainer:/data -v /var/run/docker.sock:/var/run/docker.sock --name dev-portainer portainer/portainer

参数说明:

  • -d #容器在后台运行

  • -p 9000:9000 # 宿主机9000端口映射容器中的9000端口

  • -v /var/run/docker.sock:/var/run/docker.sock # 把宿主机的Docker守护进程(docker daemon)默认监听的Unix域套接字挂载到容器中

  • -v /root/portainer:/data # 把宿主机目录 /root/portainer 挂载到容器 /data 目录;

  • –name dev-portainer # 指定运行容器的名称

具体步骤可以参考我的另一篇文章:

https://www.yuque.com/docs/share/9214ef22-4760-405c-a7bc-cc84ea3ea872?# 《阿里云ECS实例打开端口》

浏览器登录后界面如下:

img

输入好密码后,选择local登录,首页如下:

img

点击上图的local后,可以看到服务器上的docker镜像和容器的相关信息,如下:

img

上图可以看到,local上有3个镜像(Images),5个容器(Containers),点击Images,如下:

img

点击Containers,如下:

img

6、容器数据卷

6.1 容器卷的概念

首先说一下容器数据卷的背景:

(1)容器内的数据需要做持久化。

docker容器运行的时候,会产生一系列的文件或者数据,比如MySql容器运行期间肯定会产生数据,这些数据有些需要做持久化到宿主机,如果删除了容器,那容器里的数据自然就没有了,因此需要一种技术,即使容器被删除,容器里的数据依然能保留下来;

(2)在宿主机上对容器内的文件进行操作

有时现在宿主机上就可以对容器内部的文件或者配置进行更新,而不需要进入容器内的目录去更新,此时需要将宿主机上的目录和容器内的目录连通(做一个映射),因此需要一种技术,在宿主机上就可以对容器内的文件执行更新创建的操作;

(3)容器间的数据需要做共享。

比如MySql容器,有时需要将MySQL01、MySQL02、MySQL03等不同的MySQL容器间的数据共享,比如在MySQL01中产生的数据可以同步给其他MySQL容器,因此需要一种容器间的数据同步技术。

针对第一、第二种场景,有6.2的容器数据卷技术;针对第二种场景,有6.4的数据卷容器的技术。这两个名字好像只是倒过来,但含义还是有不一样的地方。

6.2 使用数据卷

使用容器数据卷有三种方式:普通挂载、具名挂载和匿名挂载。

这里先介绍2个有关数据卷的docker命令:

# 查看当前宿主机上所有的卷
docker volume ls

# 查看某个具体卷的信息
docker volume inspect 卷名

宿主机上docker的volume的保存地址:

/var/lib/docker/volumes

6.2.1 普通挂载

命令:

docker run -it --name="容器名" -v 宿主机目录:容器目录

举例:

# 创建并运行一个名字为centos01的容器,并将容器的/home/testv目录挂载到宿主机的/home/volumn目录
docker run -it --name="centos01" -v /home/volumn:/home/testv centos

测试:

在容器centos01的/home/testv目录下新建一个名字为cfile的文件,在宿主机的/home/volumn目录下可以看到相同的cfile文件,如图:

img

img

需要注意:可以在docker run命令后一次性跟多个-v,即一次性挂载多个目录,下同。

6.2.2 匿名挂载

即创建容器挂载卷时,不指定卷的具体名字,卷名缺省。

命令:

docker run -it --name="容器名" -v 容器内的路径

举例:

# 创建并运行容器centos02,创建一个匿名卷,匿名卷对应的cnetos02容器内的路径是/home
docker run -it --name="centos02" -v /home centos

结果:

img

匿名卷会在宿主机本地创建一个随机数作为VOLUME NAME。

6.2.3 具名挂载

即创建容器挂载卷时,指定卷的具体名字。

命令:

docker run -it --name "容器名" -v 卷名:容器内的路径

举例:

# 创建并运行容器centos03,创建一个具名卷,具名卷对应的cnetos03容器内的路径是/home,且具名卷的卷名为spec_volume
docker run -it --name "centos03" -v spec_volume:/home centos

结果:

img

查看具名卷spec_volume的信息,如下:

img

cd到volume的路径下,可以看到刚才我们创建的匿名卷和具名卷在该路径下:

img

具名卷相比匿名卷使用的更多。

6.3 实战:安装MySql

6.4 数据卷容器

上面说到了数据卷容器是为了实现容器间的数据同步。这里要引入一个概念:父容器。父容器也叫数据卷容器,是指所有容器要同步的那个最初的容器,比如先创建了容器mysql01,后面使用--volumes-from创建了容器mysql02、mysql03...使这些后来创建的容器都挂载第一个容器mysql01,即后面的02、03...容器都共享01容器的目录,那这个01容器就是父容器,也叫数据卷容器。

命令:

docker run -it --name="子容器名" --volumes-from 父容器名 镜像名

举例:

# 创建并运行父容器centos01
docker run -it --name="centos01" centos

# 创建并运行子容器centos02,并挂载centos
docker run -it --name="centos02" --volumes-from centos01 centos

测试:

有问题???

7、DockerFile

7.1 DockerFile基本概念

DockerFile是用来构建docker镜像的文本文件,将构建镜像的步骤通过DockerFile的命令一步一步写在DockerFile文件里。

一般构建Docker镜像的步骤如下:

  1. 编写DockerFile文件;

  2. docker build将第一步写好的DockerFile文件构建为一个镜像;

  3. docker run 运行镜像;

  4. docker push 发布镜像,可以发布到DockerHub,也可以发布到私有仓库中。

去dockerhub的官网,搜索centos的镜像,会跳转到github上,centos镜像的其中一个版本的DockerFile如下:

FROM scratch
ADD centos-8-x86_64.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="20201204"
CMD ["/bin/bash"]

随着容器概念的流行和Docker这个容器思想具体的落地,企业交付项目或者微服务都是以Docker镜像交付,而不是之前的jar包war包。对于SpringBoot开发的微服务,一般会在项目目录里有个Docker目录,里面有着DockerFile文件或者DockerFile.template文件等跟Docker相关的文件;另外在项目目录里还会有个deployment目录,里面具体会有script目录(一般放一些初始化shell脚本和相关的sql初始化脚本)等。在后面会讲到具体的springBoot项目如何打包成一个Docker镜像并push到仓库。

7.2 DockerFile指令

7.2.1 DockerFile基础知识

DockerFile的一些基础知识如下:

  • 每个指令关键字必须是大写字母;

  • DockerFile中的指令是从上到下顺序执行的;

  • '#'表示注释;

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

  • 一般企业有完整的一套devops自动化流水线进行编译构建打包工作,DockerFile中的一些变量会写成${参数名}的形式通过流水线构建时指定的参数值进行具体的构建。

7.2.2 DockerFile基本指令

(1)FROM

一般在DockeFile的第一行都会写FROM,FROM后面是基础镜像名称,也即你当前构建的镜像的父镜像,一般企业开发里FROM后面会跟公司的私服中的镜像名称和版本号。

FROM <IMAGE>
FROM <IMAGE>:<TAG>

(2)MAINTAINER

备注该镜像创建者的信息,一般是姓名 + 邮箱。

MAINTAINER <NAME>

(3)ADD

将要添加到当前构建的镜像中的组件或者服务通过ADD指令添加进镜像中,在微服务中一般ADD添加的是微服务的名称。

ADD <src> <dest>

(4)ENV

设置环境变量,通过key-value键值对的形式设置。

ENV <KEY> <VALUE>
ENV <KEY>=<VALUE>

举例:

ENV JAVA_HOME /usr/local/jdk1.8.0_11

(5)WORKDIR

在容器内部设置工作目录,当你进入该镜像生成的容器时,输入pwd就会显示这个WORKDIR指定的工作目录,即一进容器就是进入了这个工作目录中,ENTRYPOINT和CMD指定的命令都会在容器的工作目录下进行。

WORKDIR /workdir

DockerFile里可以先用ENV配置环境变量,再用 $环境变量名 来获取这个环境变量的值,如下:

ENV MYPATH /usr/local
WORKDIR $MYPATH

(6)VOLUME

用于向容器添加卷,可以提供共享存储等功能。

VOLUME ['/data']

(7)EXPOSE

指定运行该镜像的容器使用的端口,可以是多个。通过EXPOSE将这个端口指定好,启动该镜像生成的容器时就不用了-p启动了。

EXPOSE <PORT>

(8)CMD

CMD <command> (shell模式)
CMD [ "executable", "param1", "param2" ] (exec模式)
CMD [ 'param1', 'param2'] (通常与ENTRYPOINT搭配指定ENTRYPOINT的默认参数)
  • 指定容器启动时,每个DockerFile只能有一条CMD命令,如果指定了多条CMD命令,只有最后一条CMD命令会被执行;
  • 如果用户启动容器时执行了运行的命令,即docker run -it name '容器名' ``**运行命令**,则运行命令会覆盖掉CMD指定的命令。

(9)ENTRYPOINT

指定镜像生成的容器启动时要运行的命令,可以追加命令。

ENTRYPOINT <command> (shell模式)
ENTRYPOINT [ "executable", "param1", "param2" ] (exec模式)
  • 与CMD不同,ENTRYPOINT指定的命令不会被docker run后面拼接的命令覆盖,而是将拼接的命令当做ENTRYPOINT指定命令的参数;
  • 每个DockerFILE只能有一个ENTRYPOINT命令,当指定多个时,只有最后一个生效。

(10)RUN

用于指定构建镜像时运行的命令,两种模式:

RUN <command> (shell模式)
RUN [ "executable", "param1", "param2" ] (exec模式)
  • 在shell模式下,是使用/bin/sh -c COMMAND来运行命令的;
  • 在exec模式下可以指定其他的shell来运行命令RUN [“/bin/bash”, “-c”, “echo hello”]。

多条RUN指令可以合并为一条,用&&分隔,举例:

RUN yum install httpd && yum install ftp

(11)ONBUILD

为镜像创建触发器,当一个镜像被用作其他镜像的基础镜像时,这个触发器会被执行,当子镜像构建时会插入触发器中的指令。

# 举例
ONBUILD COPY index.html /var/www/html

(12)COPY

跟ADD一样,作用都是将文件或目录复制到Dockerfile构建的镜像中,ADD包含了类似tar的解压功能,如果只是单纯复制文件,建议使用COPY,而且,两者的源文件路径使用Dockerfile相对路径,目标路径使用绝对路径。

COPY <src> <dest>

当前项目中的DockeFile也很简单,只用到了FROM、ADD、ENTRYPOINT、RUN、ENV、EXPOSE、WORKDIR这几个指令,ENTRYPOINT指令后面跟的是一个脚本。

7.3 DockerFile样例

以公司的DockerFile文件为例子,如下:

FROM ${DOCKER_REGISTRY_JAVA_BASE_IMAGE}
USER root
ENV LANG en_US_UTF-8
ADD ${SERVICE_NAME} /opt/{SERVICE_NAME}
EXPOSE 18088
WORKDIR /opt/{SERVICE_NAME}
ENTRYPOINT ["./startup.sh"]

startup.sh是java程序启动的脚本,里面配置了启动java程序制定的jvm参数以及jar包,如下:

#!/usr/bin/env sh
java -XX:+UseG1GC \
		 -server \
     -Duser.timezone='GMT+08:00' \
     -Duser.country=CN \
     -Djava.security.egd=file:/dev/./urandom \
     -Xms256M \
     -Xmx2048M \
     -XX:HeapDumpPath=/opt/pco-portal/log \
     -XX:-HeapDumpOnOutOfMemoryError \
     -Xdebug \
     -XX:+PrintGCDetails \
     -Xrunjdwp:transprot=dt_socket,server=y,suspend=n,address=9527 \
     -jar pco-protal.jar

这么做的目的是:启动java程序时想更细粒度的控制java程序,比如设置JVM参数等,把这些参数变量统一在sh脚本里设置,解耦美观。

8、发布镜像

大家平时工作的时候肯定是将镜像发布到公司的私有镜像仓库中,平时在家做的项目可以发布到DockerHub或者阿里云镜像仓库中。

8.1 发布镜像到DockerHub

(1)登录dockerhub

docker login

结果:

[root@iZ2vcf0atudng4otf06u38Z ~]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: woshuangguoyang
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

登录好后上传镜像:

docker push 镜像名称:版本号TAG

8.2 发布镜像到阿里云

DockerHub是外网,一般很慢,方便的话可以发布到阿里云镜像仓库。

(1)创建命名空间

命名空间作为一些仓库的集合,推荐将一个公司或组织的仓库集中在一个命名空间下面,即一个命名空间底下对应多个仓库,更多阿里云容器镜像服务命名空间的介绍参考:https://help.aliyun.com/document_detail/60765.html

img

(2)创建镜像仓库

创建镜像仓库时有个地方记得选择本地。

img

创建好仓库后,可以点击进入仓库,可以看到仓库的基本信息和操作指南,不得不说阿里的文档说明和帮助指南是真的详细易懂,如图:

img

(3)虚拟机登录阿里云镜像仓库

[root@iZ2vcf0atudng4otf06u38Z ~]# sudo docker login --username=15828074705 registry.cn-shenzhen.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

(4)给要上传的镜像打TAG

先看宿主机下有哪些镜像:

[root@iZ2vcf0atudng4otf06u38Z ~]# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mysql                 5.7                 697daaecf703        7 days ago          448MB
centos                latest              300e315adb2f        11 days ago         209MB
portainer/portainer   latest              62771b0b9b09        4 months ago        79.1MB
hello-world           latest              bf756fb1ae65        11 months ago       13.3kB

把hello-world:latest镜像推到我们第二步创建的镜像仓库中,先给hello-world:latest镜像打TAG:

sudo docker tag hello-world:latest registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo:0.1

格式是:

sudo docker tag 宿主机待上传的镜像仓库:版本号 阿里云上仓库地址:版本号

(5)向阿里云镜像仓库上传镜像

sudo docker push registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo:0.1

结果:

[root@iZ2vcf0atudng4otf06u38Z ~]# sudo docker push registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo:0.1
The push refers to repository [registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo]
9c27e219663c: Mounted from docker_study_jerry/docker_study_repo/my-hello-world 
0.1: digest: sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 size: 525

打开阿里云镜像仓库的控制台,可以看到我们在第二步创建的仓库中已经上传了镜像:

img

(6)从阿里云镜像仓库下载镜像

第一步还是要登录,然后再用docker pull拉取镜像,比如拉取上面仓库中的镜像:

# 使用方法
sudo docker pull 阿里云镜像仓库地址:版本号

# 举例,拉取上面的镜像
sudo docker pull hello-world:latest registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/docker_study_repo:0.1

再一次感叹阿里云文档之详细易懂为用户使用考虑。

9、Docker网络

9.1 Docker网络概述

安装 Docker 以后,会默认创建三种网络,可以通过 docker network ls 查看。

img

上图有Docker网络的三种模式,其实Docker网络一共有四种模式:

网络模式 简介
bridge Docker的默认网络模式,采用veth-pair技术,为每一个容器分配一个ip,并将容器连接到docker守护进程创建的docker0的虚拟网桥,容器通过这个docker0虚拟网桥连通。
host 容器使用宿主机的ip和端口,容器本身不会虚拟出网卡,配置自己的ip等。
none 容器有独立的network namespace,但并没有对其进行任何网络设置,如分配veti pair和网桥连接、Ip等。
container 新创建的容器不会创建自己的网卡,配置自己的ip,而是和一个指定的容器共享ip、端口范围等。

上面四种模式中,host模式和none模式不常用,container模式在容器网络连通里讲,这里主要介绍一下Docker默认的网络模式:桥接模式(bridge)。

桥接模式

桥接模式是Docker网络的默认模式,在该模式下,Docker守护进程会创建一个虚拟网桥docker0,每创建一个容器时,Docker守护进程会创建一对对等的虚拟设备接口veth pari,将其中一个接口设置为容器的eth0接口(容器的虚拟网卡),另一个接口放置在宿主机的命名空间中,以类似veth***这样的名字命名,从而将宿主机上的所有容器都连接到这个docker0网桥上,进而使容器间互相通信。

下面通过具体例子来说明桥接模式:

(1)查看宿主机上的docker网络

ip addr

img

(2)启动第一个tomcat容器

# 后台运行第一个tomcat容器tomcat01
docker run -d -P --name tomcat01 tomcat

# 查看tomcat01容器的ip信息
docker exec -it tomcat01 ip addr

img

(3)启动第二个tomcat容器

# 后台运行第二个tomcat容器tomcat02
docker run -d -P --name tomcat02 tomcat

# 查看tomcat02容器的ip信息
docker exec -it tomcat02 ip addr

img

(4)再观察宿主机的网络信息

ip addr

img

上面两个tomcat容器和宿主机上网络的桥接模式的结构图如下,需要注意的是tomcat01容器和tomcat02容器之间网络连通都要经由docker0转发

img

最后总结一下Bridge桥接模式的实现步骤:

  1. Docker Daemon 利用 veth pair 技术,在宿主机上创建一对对等虚拟网络接口设备,假设为 veth0 和 veth1。而 veth pair 技术的特性可以保证无论哪一个 veth 接收到网络报文,都会将报文传输给另一方;

  2. Docker Daemon 将 veth0 附加到 Docker Daemon 创建的 docker0 网桥上,保证宿主机的网络报文可以发往 veth0;

  3. Docker Daemon 将 veth1 添加到 Docker Container 所属的 namespace 下,并被改名为 eth0。如此一来,宿主机的网络报文若发往 veth0,则立即会被 Container 的 eth0 接收,实现宿主机到 Docker Container 网络的联通性;同时,也保证 Docker Container 单独使用 eth0,实现容器网络环境的隔离性。

思考一个场景:docker新启动一个容器时,会给这个容器分配一个ip地址,但在某些场景,比如MyBaits里的数据库链接里将这个ip写死了,如果新启动容器后这个ip变了,会导致数据库连接不上,此时需要指定容器名去连接,即ping的是容器名而不是容器的ip,这样即使容器的ip变了,但是容器名不变,依然可以通过容器名去ping通网络。上一节介绍的docker默认的桥接模式docker0并不支持通过容器名访问容器。

实现上面的场景有两种方法:

  • link
  • 自定义网络连接

其中第一种方法已经不推荐使用了,推荐使用第二种方法。

link是在创建并启动容器的时候指定的。

方法:

docker run -d -P --name 容器名1 --link 容器名2 镜像名

上述通过--link创建的容器1,可以通过容器名ping通容器2,但反过来,容器2依然不能ping通容器1,如下:

[root@iZ2vcf0atudng4otf06u38Z ~]# docker run -d -P --name tomcat03 --link tomcat02 tomcat
e493a6ee1baa794484b47b97fb03c134432fab01d18fa28a46818331e2e2c1aa
[root@iZ2vcf0atudng4otf06u38Z ~]# 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.133 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=2 ttl=64 time=0.101 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=3 ttl=64 time=0.084 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=4 ttl=64 time=0.084 ms
64 bytes from tomcat02 (172.17.0.3): icmp_seq=5 ttl=64 time=0.085 ms
^C
--- tomcat02 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 80ms
rtt min/avg/max/mdev = 0.084/0.097/0.133/0.020 ms
[root@iZ2vcf0atudng4otf06u38Z ~]# docker exec -it tomcat02 ping tomcat03
^C[root@iZ2vcf0atudng4otf06u38Z ~]# ^C
[root@iZ2vcf0atudng4otf06u38Z ~]# 

--link的原理是:在容器1的/etc/hosts里配置了容器2的ip地址,如下:

[root@iZ2vcf0atudng4otf06u38Z ~]# docker exec -it tomcat03 cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	tomcat02 6fe98b6b9c81
172.17.0.4	e493a6ee1baa

上面的“172.17.0.3 tomcat02 6fe98b6b9c81”信息就是在容器tomcat03的/etc/hosts里配置的容器tomcat02的ip地址,而在tomcat02的/etc/hosts文件里并没有tomcat03的ip地址,这也是为什么只能tomcat03 ping通tomcat02,而tomcat02 ping不通tomcat03。

[root@iZ2vcf0atudng4otf06u38Z ~]# docker exec -it tomcat02 cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.17.0.3	6fe98b6b9c81

再次强调:已经不推荐使用--link了,具体原因俺也不清楚...

9.2.2 自定义网络

就是使用docker network create创建网络,见3.4.4小节。

先创建一个自定义网络mynet,再使用mynet创建两个容器tomcat01和tomcat02,如下:

[root@iZ2vcf0atudng4otf06u38Z ~]# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
7f12423b5112        bridge              bridge              local
a70143b200b4        host                host                local
efaf028a11ad        mynet               bridge              local
7cd362f44529        none                null                local
[root@iZ2vcf0atudng4otf06u38Z ~]# docker run -d -P --name tomcat01 --net mynet tomcat
3372bc309f274dadf5e43e682d7278ca5c0d00b2bd9bfab9e8789c5dd15d8b82
^[[A[root@iZ2vcf0atudng4otf06u38Z ~]# docker run -d -P --name tomcat02 --net mynet tomcat
3ab0d3173b156f87f8b3213544471b804ee383c45a8247dbfccf4b12c586fd82
[root@iZ2vcf0atudng4otf06u38Z ~]# 

使用容器名去ping容器,tomcat01和tomcat02均可以通过对方的容器名去ping通网络,如图:

[root@iZ2vcf0atudng4otf06u38Z ~]# docker exec -it tomcat01 ping tomcat02
PING tomcat02 (192.168.0.3) 56(84) bytes of data.
64 bytes from tomcat02.mynet (192.168.0.3): icmp_seq=1 ttl=64 time=0.108 ms
64 bytes from tomcat02.mynet (192.168.0.3): icmp_seq=2 ttl=64 time=0.098 ms
64 bytes from tomcat02.mynet (192.168.0.3): icmp_seq=3 ttl=64 time=0.099 ms
^C
--- tomcat02 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 48ms
rtt min/avg/max/mdev = 0.098/0.101/0.108/0.012 ms
[root@iZ2vcf0atudng4otf06u38Z ~]# docker exec -it tomcat02 ping tomcat01
PING tomcat01 (192.168.0.2) 56(84) bytes of data.
64 bytes from tomcat01.mynet (192.168.0.2): icmp_seq=1 ttl=64 time=0.087 ms
64 bytes from tomcat01.mynet (192.168.0.2): icmp_seq=2 ttl=64 time=0.084 ms
64 bytes from tomcat01.mynet (192.168.0.2): icmp_seq=3 ttl=64 time=0.091 ms
^C
--- tomcat01 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 29ms
rtt min/avg/max/mdev = 0.084/0.087/0.091/0.008 ms
[root@iZ2vcf0atudng4otf06u38Z ~]# 

结论:通过docker network create自定义网络已经帮我们解决了通过容器名去互相ping通对方容器,与--link不同,自定义网络是可以双向ping通的。

9.3 网络连通

见3.4.5小节。

10、SpringBoot微服务打包成Docker镜像

SpringBoot开发的微服务从java程序到最后部署到容器中能够提供服务,需要以下步骤:

  1. 构建SpringBoot项目;

  2. 将项目打包;

  3. 编写项目的DockerFile;

  4. 构建镜像;

  5. 将镜像上传到镜像仓库;

  6. 从仓库中拉取镜像;

  7. 基于镜像启动容器;

  8. 使用容器提供的服务(调接口)。

10.1 构建SpringBoot项目

IDEA创建一个SpringBoot项目,举个例子,写一个简单的Controller,并本地启动访问,如下:

package com.docker.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping("/dockerString")
    public String getHello()
    {
        return "Hello Docker";
    }
}

项目对应的端口为18088:

server:
  port: 18088

启动项目后,postman调用接口,如下:

img

10.2 将项目打包

使用的Gradle项目,点击build,打出的jar包路径如下:

img

10.3 编写DockerFile

FROM java:8
ENV MYPATH /opt/docker-compose
WORKDIR $MYPATH
ADD *.jar /docker-compose-app.jar
EXPOSE 18088
ENTRYPOINT ["java", "-jar", "/docker-compose-app.jar"]
#!/usr/bin/env sh
java -XX:+UseG1GC \
		 -server \
     -Duser.timezone='GMT+08:00' \
     -Duser.country=CN \
     -Xms256M \
     -Xmx2048M \
     -XX:-HeapDumpOnOutOfMemoryError \
     -Xdebug \
     -XX:+PrintGCDetails \
     -jar myapp.jar

因为10.4里打算把DockerFile、startup.sh和jar包放在同一个目录,因此ADD .jar /myapp.jar里jar包的路径是当前目录.jar,startup.sh是./。

10.4 本地构建镜像

将DockerFile、startup.sh和jar包通过xftp上传到阿里云服务器上的同一个目录中,如图:

img

改变startup.sh的权限,加上-x:

chmod +x startup.sh

将startup.sh脚本由dos格式转换成unix格式,否则报错:Unable to access jarfile myapp.jar

dos2unix startup.sh

构建Docker镜像:

docker build -t myapp:0.1 .

如图:

img

可以看到DockerFile中的每一条指定都会打印在控制台中。

此时,docker images查看本地所有镜像,可以看到我们构建好的镜像,注意镜像大小如果是0就是有问题:

img

10.5 将镜像上传到镜像仓库

之前介绍过如何将镜像上传到阿里云镜像仓库,这里结果如下:

img

进入上面创建的容器仓库,按照提示的命令一步一步敲:

sudo docker tag a86ee46429c1 registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/springboot-app:0.1
sudo docker push registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/springboot-app:0.1

最后结果:

img

img

10.6 从仓库中拉取镜像

清空本地所有的镜像,从10.5上传到的阿里云镜像仓库里拉取我们刚上传的镜像:

sudo docker pull registry.cn-shenzhen.aliyuncs.com/docker_study_jerry/springboot-app:0.1

结果如图:

img

10.7 基于镜像启动容器

首先启动容器,指定宿主机和容器的端口映射:8080:18088:

docker run -p 8080:18088 --name "springboot-web-app" myapp:0.1

在控制台中会打印springboot项目的启动日志,如图:

img

img

10.8 使用容器提供的服务

首先在内网通过iptables打开宿主机的8080端口,然后在阿里云的ECS实例里的安全组里把端口8080放通,可以参考:https://www.yuque.com/docs/share/9214ef22-4760-405c-a7bc-cc84ea3ea872?# 《阿里云ECS实例打开端口》,

使用postman调用springboot项目里的HelloController接口,如图:

img

至此springBoot项目打包成镜像部署在Docker容器中并能提供服务的步骤介绍完毕。

11、Docker Compose

11.1 Docker Compose简介

前面介绍了Docker的一些基本知识,尤其是第十节介绍了SpringBoot项目如何打包成镜像用容器去启动,但是现实场景是:企业的一套完整的服务必定是由若干微服务组成,Docker推荐每个容器里部署一个微服务,难道要手动去执行docker run去将这些微服务所在的容器一个个启动?肯定是不行的,Docker Compose就是针对上述容器管理难题提供解决方案的,Docker Compose通过yaml配置文件来定义运行多个容器,从而轻松高效地管理众多容器。这种通过配置文件去管理众多容器的方式被称为容器编排,容器编排更高级的解决方案应该是K8S

Docker Compose官网:https://docs.docker.com/compose/

这里摘取官网对Docker Compose的概述:

Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application’s services. Then, with a single command, you create and start all the services from your configuration. To learn more about all the features of Compose, see the list of features.

Compose works in all environments: production, staging, development, testing, as well as CI workflows. You can learn more about each case in Common Use Cases.

Using Compose is basically a three-step process:

  1. Define your app’s environment with a Dockerfile so it can be reproduced anywhere.

  2. Define the services that make up your app in docker-compose.yml so they can be run together in an isolated environment.

  3. Run docker-compose up and Compose starts and runs your entire app.

A docker-compose.yml looks like this:

version: "3.9"  # optional since v1.27.0
services:
  web:
    build: .
    ports:
      - "5000:5000"
    volumes:
      - .:/code
      - logvolume01:/var/log
    links:
      - redis
  redis:
    image: redis
volumes:
  logvolume01: {}

补充:docker-compose是 docker 官方的开源项目,使用 python 编写。

11.2 Docker Compose安装

官网有详细的步骤:https://docs.docker.com/compose/install/

1、下载Docker Compose:

sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

默认是将Docker Compose下载到了/usr/local/bin/docker-compose目录下。

2、修改目录为可执行目录:

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

3、测试Docker Compose是否安装成功:

docker-compose version

如果是下图情况,说明Docker Compose安装成功。

img

11.3 Docker Compose初体验

官网参考连接:https://docs.docker.com/compose/gettingstarted/

11.4 Docker Compose常用命令

Docker Compose跟Docker一样,也有一套自己的命令,下面介绍一下Docker Compose的常用命令。

11.4.1 docker-compose命令格式

docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]

选项如下:

-f,–file FILE     指定Compose模板文件,默认为docker-compose.yml,可以多次指定。

-p,–project-name  NAME指定项目名称,默认将使用所在目录名称作为项目名。

-x-network-driver   使用Docker的可拔插网络后端特性(需要Docker 1.9+版本)

-x-network-driver    DRIVER指定网络后端的驱动,默认为bridge(需要Docker 1.9+版本)

-verbose        出更多调试信息

-v,–version      打印版本并退出

11.4.2 docker-compose up

启动Docker Compose服务:

docker-compose up [options] [--scale SERVICE=NUM...] [SERVICE...]

选项如下:

  • -d:在后台运行服务容器

  • –no-color:不使用颜色来区分不同的服务的控制输出

  • –no-deps:不启动服务所链接的容器

  • –force-recreate:强制重新创建容器,不能与–no-recreate同时使用

  • –no-recreate:如果容器已经存在,则不重新创建,不能与–force-recreate同时使用

  • –no-build:不自动构建缺失的服务镜像

  • –build:在启动容器前构建服务镜像

  • –abort-on-container-exit:停止所有容器,如果任何一个容器被停止,不能与-d同时使用

  • -t, –timeout TIMEOUT:停止容器时候的超时(默认为10秒)

  • –remove-orphans:删除服务中没有在compose文件中定义的容器

  • –scale SERVICE=NUM:设置服务运行容器的个数,将覆盖在compose中通过scale指定的参数

举例:

# 启动所有服务
docker-compose up

# 在后台所有启动服务
docker-compose up -d

# -f 指定使用的Compose模板文件,默认为docker-compose.yml,可以多次指定。
docker-compose -f docker-compose.yml up -d

11.4.3 docker-compose ps

列出项目中所有的的容器:

docker-compose ps [options] [SERVICE...]

举例:

# 列出项目中目前的所有容器
docker-compose ps

11.4.4 docker-compose start

# 启动已经存在的服务容器
docker-compose start [SERVICE...]

11.4.5 docker-compose stop

停止正在运行的容器:

docker-compose stop [options] [SERVICE...]

选项包括:

  • -t, –timeout:TIMEOUT 停止容器时候的超时(默认为10秒)

举例:

# 停止正在运行的名为nginx的容器,可以通过docker-compose start 再次启动
docker-compose stop nginx

11.4.6 docker-compose restart

# 重启项目中的服务
docker-compose restart [options] [SERVICE...]

选项包括:

  • -t, –timeout TIMEOUT:指定重启前停止容器的超时(默认为10秒)

11.4.7 docker-compose pause

# 暂停一个服务容器
docker-compose pause [SERVICE...]

11.4.8 docker-compose unpause

# 恢复处于暂停状态中的服务
docker-compose unpause [SERVICE...]

11.4.9 docker-compose kill

# 通过发送SIGKILL信号来强制停止服务容器
docker-compose kill [options] [SERVICE...]

举例:

# 支持通过-s参数来指定发送的信号,例如通过如下指令发送SIGINT信号:
docker-compose kill -s SIGINT

11.4.10 docker-compose -h

查看Docker Compose的帮助文档

# 查看帮助
docker-compose -h

11.4.11 docker-compose down

# 停止和删除容器、网络、卷、镜像。
docker-compose down [options]

选项包括:

  • –rmi type:删除镜像,类型必须是all,删除compose文件中定义的所有镜像;local,删除镜像名为空的镜像

  • -v, –volumes:删除已经在compose文件中定义的和匿名的附在容器上的数据卷

  • –remove-orphans:删除服务中没有在compose中定义的容器

举例:

# 停用移除所有容器以及网络相关
docker-compose down

11.4.12 docker-compose create

# 为服务创建容器
docker-compose create [options] [SERVICE...]

选项包括:

  • –force-recreate:重新创建容器,即使配置和镜像没有改变,不兼容–no-recreate参数

  • –no-recreate:如果容器已经存在,不需要重新创建,不兼容–force-recreate参数

  • –no-build:不创建镜像,即使缺失

  • –build:创建容器前,生成镜像

11.4.13 docker-compose run

# 在指定服务上执行一个命令
docker-compose run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]

举例:

# 在服务名为ubuntu的容器上执行一个ping命令
docker-compose run ubuntu ping www.baidu.com

11.4.14 docker-compose exec

# 进入某个服务所在的容器
docker-compose exec [options] SERVICE COMMAND [ARGS...]

选项包括:

  • -d:分离模式,后台运行命令

  • –privileged: 获取特权

  • –user USER:指定运行的用户

  • -T:禁用分配TTY,默认docker-compose exec分配TTY

  • –index=index:当一个服务拥有多个容器时,可通过该参数登陆到到该服务下的任何服务,例如:docker-compose exec –index=1 web /bin/bash ,web服务中包含多个容器

11.4.15 docker-compose rm

# 删除所有(停止状态的)服务容器,推荐先执行docker-compose stop命令来停止容器
docker-compose rm [options] [SERVICE...]

选项包括:

  • –f, –force,强制直接删除,包括非停止状态的容器
  • -v,删除容器所挂载的数据卷

11.4.16 docker-compose build

 # 构建(重新构建)项目中的服务容器
docker-compose build [options] [--build-arg key=val...] [SERVICE...]

选项包括:

  • –compress:通过gzip压缩构建上下环境

  • –force-rm: 删除构建过程中的临时容器

  • –no-cache:构建镜像过程中不使用缓存

  • –pull:始终尝试通过拉取操作来获取更新版本的镜像

  • -m, –memory MEM:为构建的容器设置内存大小

  • –build-arg key=val:为服务设置build-time变量

服务容器一旦构建后,将会带上一个标记名。可以随时在项目目录下运行docker-compose build来重新构建服务

11.4.17 docker-compose pull

# 拉取服务依赖的镜像
docker-compose pull [options] [SERVICE...]

选项包括:

  • –ignore-pull-failures:忽略拉取镜像过程中的错误

  • –parallel:多个镜像同时拉取

  • –quiet:拉取镜像过程中不打印进度信息

11.4.18 docker-compose push

# 推送服务依赖的镜像
docker-compose push [options] [SERVICE...]

选项包括:

  • –ignore-push-failures:忽略推送镜像过程中的错误

11.4.19 docker-compose logs

# 查看服务容器的输出。默认情况下,docker-compose将对不同的服务输出使用不同的颜色来区分。可以通过–no-color来关闭颜色
docker-compose logs [options] [SERVICE...]

举例:

# 查看服务名为nginx的容器日志,关闭颜色区分
docker compose -no-color nginx

# 查看nginx服务的实时日志
docker-compose logs -f nginx

11.4.20 docker-compose scale

指定服务的容器副本数

# 设置指定服务运行的容器个数,通过service=num的参数来设置数量
docker-compose scale web=3 db=2

11.4.21 dokcer-compose config

# 验证并查看compose文件配置
docker-compose config [options]

选项包括:

  • –resolve-image-digests:将镜像标签标记为摘要

  • -q, –quiet:只验证配置,不输出。 当配置正确时,不输出任何内容,当文件配置错误,输出错误信息

  • –services:打印服务名,一行一个

  • –volumes :打印数据卷名,一行一个

11.4.22 docker-compose port

# 显示某个容器端口所映射的公共端口
docker-compose port [options] SERVICE PRIVATE_PORT

选项包括:

  • –protocol=proto:指定端口协议,TCP(默认值)或者UDP
  • –index=index:如果同意服务存在多个容器,指定命令对象容器的序号(默认为1)

11.4.23 docker-compose version

# 打印docker compose的版本信息
docker-compose version

11.5 Docker Compose配置编写规则

11.1小节已经展示了一个简单的Docker-Compose.yaml的示例,一个Docker-Compose.yaml由三部分组成:

  • version:Docker-Compose的版本,目前最新的是3;

  • services:整个服务由哪些微服务组成,即被编排的容器微服务;

  • 其他:这一部分主要是整个Docker Compose的环境,最常见的有volumes和networks。

举一个稍微复杂的Docker-Compose.yaml:

version: '3'
services:
  mongodb3.2:
    container_name: mongodb3.2
    image: xxxxxx/library/zujuan_mongodb3.2:v1
    ports:
      - "16016:27017"
    volumes:
      - "/Docker-Ser/Mongodb/data/db:/data/db:rw"
      - "/Docker-Ser/Mongodb/data/backup:/data/backup:rw"
    networks:
      mall-network:
        aliases:
          - mongodb3.2
  redis:
    container_name: redis
    image: xxx:5588/library/zujuan_redis:v1
    ports:
      - "7480:6379"
    networks:
      mall-network:
        aliases:
          - redis
    depends_on:
      - mongodb3.2

  math_engine:
    container_name: math_engine
    image: xxx:5588/library/engine:v1
    ports:
      - "7101"
    volumes:
      - "/Docker-Ser/math_engine/www:/home/www:rw"
      - "/Docker-Ser/math_engine/supervisor.conf.d:/etc/supervisor.conf.d:rw"
      - "/Docker-Ser/math_engine/logs/math_engine.log:/tmp/math_engine.log:rw"
    networks:
      mall-network:
        aliases:
          - math_engine
    depends_on:
      - mongodb3.2

  en_engine:
    container_name: en_engine
    image: xxx:5588/library/engine:v1
    volumes:
      - "/Docker-Ser/en_engine/www:/home/www:rw"
      - "/Docker-Ser/en_engine/supervisor.conf.d:/etc/supervisor.conf.d:rw"
      - "/Docker-Ser/en_engine/logs/en_engine.log:/tmp/en_engine.log:rw"
    ports:
      - "7001"
    networks:
      mall-network:
        aliases:
          - en_engine
    depends_on:
      - mongodb3.2

  zujuan_qt:
    container_name: zujuan_qt
    image: xxx:5588/library/zujuan_qiantai:v1
    ports:
      - "80:80"
    volumes:
      - "/Docker-Ser/Pyweb/www:/home/www:rw"
      - "/Docker-Ser/Pyweb/supervisor.conf.d:/etc/supervisor.conf.d:rw"
      - "/Docker-Ser/Pyweb/log:/mnt/log:rw"
      - "/Docker-Ser/Nginx/conf.d:/etc/nginx/conf.d:rw"
    networks:
      mall-network:
        aliases:
          - zujuan_qt
    depends_on:
      - mongodb3.2
      - redis
      - math_engine
      - en_engine

networks:
  mall-network:
    driver: bridge

下面详细介绍一下Docker-Compose.yaml的书写规则。

11.5.1 image

services:
  web:
    image: hello-world

在 services 标签下的第二级标签是 web,这个名字是用户自己自定义,它就是服务名称。image 则是指定服务的镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会从仓库中尝试拉取这个镜像。

例如下面这些格式都是可以的:

image: redis
image: ubuntu:14.04
image: tutum/influxdb
image: example-registry.com:4000/postgresql
image: a4bc65fd

11.5.2 build

服务除了可以基于指定的镜像,还可以基于一份 Dockerfile,在使用 up 启动之时执行构建任务,这个构建标签就是 build,它可以指定 Dockerfile 所在文件夹的路径。Compose 将会利用它自动构建这个镜像,然后使用这个镜像启动服务容器。

build: /path/to/build/dir

也可以是相对路径,只要上下文确定就可以读取到 Dockerfile。

build: ./dir

设定上下文根目录,然后以该目录为准指定 Dockerfile。

build:
  context: ../
  dockerfile: path/of/Dockerfile

注意 build 都是一个目录,如果你要指定 Dockerfile 文件需要在 build 标签的子级标签中使用 dockerfile 标签指定,如上面的例子。如果你同时指定了 image 和 build 两个标签,那么 Compose 会构建镜像并且把镜像命名为 image 后面的那个名字。

build: ./dir
image: webapp:tag

既然可以在 docker-compose.yml 中定义构建任务,那么一定少不了 arg 这个标签,就像 Dockerfile 中的 ARG 指令,它可以在构建过程中指定环境变量,但是在构建成功后取消,在 docker-compose.yml 文件中也支持这样的写法:

build:
  context: .
  args:
    buildno: 1
    password: secret

下面这种写法也是支持的,一般来说下面的写法更适合阅读。

build:
  context: .
  args:
    - buildno=1
    - password=secret

与 ENV 不同的是,ARG 是允许空值的。例如:

args:
  - buildno
  - password

11.5.3 command

使用 command 可以覆盖容器启动后默认执行的命令。

command: bundle exec thin -p 3000

也可以写成类似 Dockerfile 中的格式:

command: [bundle, exec, thin, -p, 3000]

11.5.4 container_name

前面说过 Compose 的容器名称格式是:<项目名称><服务名称><序号>

虽然可以自定义项目名称、服务名称,但是如果你想完全控制容器的命名,可以使用这个标签指定:

container_name: app

这样容器的名字就指定为 app 了。

11.5.5 depends_on

在使用 Compose 时,最大的好处就是少打启动命令,但是一般项目容器启动的顺序是有要求的,如果直接从上到下启动容器,必然会因为容器依赖问题而启动失败。

例如在没启动数据库容器的时候启动了应用容器,这时候应用容器会因为找不到数据库而退出,为了避免这种情况我们需要加入一个标签,就是 depends_on,这个标签解决了容器的依赖、启动先后的问题。

例如下面容器会先启动 redis 和 db 两个服务,最后才启动 web 服务:

version: '2'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

注意的是,默认情况下使用 docker-compose up web 这样的方式启动 web 服务时,也会启动 redis 和 db 两个服务,因为在配置文件中定义了依赖关系。

11.5.6 dns

和 --dns 参数一样用途,格式如下:

dns: 8.8.8.8

也可以是一个列表:

dns:
  - 8.8.8.8
  - 9.9.9.9

此外 dns_search 的配置也类似:

dns_search: example.com
dns_search:
  - dc1.example.com
  - dc2.example.com

11.5.7 tmpfs

挂载临时目录到容器内部,与 run 的参数一样效果:

tmpfs: /run
tmpfs:
  - /run
  - /tmp

11.5.8 entrypoint

在 Dockerfile 中有一个指令叫做 ENTRYPOINT 指令,用于指定接入点,第四章有对比过与 CMD 的区别。

在 docker-compose.yml 中可以定义接入点,覆盖 Dockerfile 中的定义:

entrypoint: /code/entrypoint.sh

格式和 Docker 类似,不过还可以写成这样:

entrypoint:
    - php
    - -d
    - zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20100525/xdebug.so
    - -d
    - memory_limit=-1
    - vendor/bin/phpunit

推荐使用第一种方式。

11.5.9 env_file

还记得前面提到的 .env 文件吧,这个文件可以设置 Compose 的变量。而在 docker-compose.yml 中可以定义一个专门存放变量的文件。

如果通过 docker-compose -f FILE 指定了配置文件,则 env_file 中路径会使用配置文件路径。

如果有变量名称与 environment 指令冲突,则以后者为准。格式如下:

env_file: .env

或者根据 docker-compose.yml 设置多个:

env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env

注意的是这里所说的环境变量是对宿主机的 Compose 而言的,如果在配置文件中有 build 操作,这些变量并不会进入构建过程中,如果要在构建中使用变量还是首选前面刚讲的 arg 标签。

11.5.10 environment

与上面的 env_file 标签完全不同,反而和 arg 有几分类似,这个标签的作用是设置镜像变量,它可以保存变量到镜像里面,也就是说启动的容器也会包含这些变量设置,这是与 arg 最大的不同。

一般 arg 标签的变量仅用在构建过程中。而 environment 和 Dockerfile 中的 ENV 指令一样会把变量一直保存在镜像、容器中,类似 docker run -e 的效果。

environment:
  RACK_ENV: development
  SHOW: 'true'
  SESSION_SECRET:
environment:
  - RACK_ENV=development
  - SHOW=true
  - SESSION_SECRET

在使用Docker过程中,我们会有许多单独使用docker run启动的容器,为了使Compose能够连接这些不在docker-compose.yml中定义的容器,我们需要一个特殊的标签,就是external_links,它可以让Compose项目里面的容器连接到那些项目配置外部的容器(前提是外部容器中必须至少有一个容器是连接到与项目内的服务的同一个网络里面)。

格式如下:

external_links:
 - redis_1
 - project_db_1:mysql
 - project_db_1:postgresql

11.5.12 extra_hosts

添加主机名的标签,就是往/etc/hosts文件中添加一些记录,与Docker client的--add-host类似:

extra_hosts:
 - "somehost:162.242.195.82"
 - "otherhost:50.31.209.229"

启动之后查看容器内部etc/hosts:

162.242.195.82  somehost
50.31.209.229   otherhost

还记得上面的depends_on吧,那个标签解决的是启动顺序问题,这个标签解决的是容器连接问题,与Docker client的--link一样效果,会连接到其它服务中的容器。

格式如下:

links:
 - db
 - db:database
 - redis

使用的别名将会自动在服务容器中的/etc/hosts里创建。例如:

172.12.2.186  db
172.12.2.186  database
172.12.2.187  redis

相应的环境变量也将被创建。

11.5.14 logging

这个标签用于配置日志服务。格式如下:

logging:
  driver: syslog
  options:
    syslog-address: "tcp://192.168.0.42:123"

默认的driver是json-file。只有json-file和journald可以通过docker-compose logs显示日志,其他方式有其他日志查看方式,但目前Compose不支持。对于可选值可以使用options指定。

有关更多这方面的信息可以阅读官方文档:

https://docs.docker.com/engine/admin/logging/overview/

11.5.15 ports

映射端口的标签。

使用HOST:CONTAINER格式或者只是指定容器的端口,宿主机会随机映射端口。

ports:
 - "3000"
 - "8000:8000"
 - "49100:22"
 - "127.0.0.1:8001:8001"

对于8080:18088, 8080是宿主机端口,18088是容器端口

注意:当使用HOST:CONTAINER格式来映射端口时,如果你使用的容器端口小于60你可能会得到错误的结果,因为YAML将会解析xx:yy这种数字格式为60进制。所以建议采用字符串格式。

11.5.16 volumes

挂载一个目录或者一个已存在的数据卷容器,可以直接使用 [HOST:CONTAINER] 这样的格式,或者使用 [HOST:CONTAINER:ro] 这样的格式,后者对于容器来说,数据卷是只读的,这样可以有效保护宿主机的文件系统。

Compose的数据卷指定路径可以是相对路径,使用 . 或者 .. 来指定相对目录。

数据卷的格式可以是下面多种形式:

volumes:
  // 只是指定一个路径,Docker 会自动再创建一个数据卷(这个路径是容器内部的)。
  - /var/lib/mysql
  // 使用绝对路径挂载数据卷
  - /opt/data:/var/lib/mysql
  //  Compose 配置文件为中心的相对路径作为数据卷挂载到容器。
  - ./cache:/tmp/cache
  // 使用用户的相对路径(~/ 表示的目录是 /home/<用户目录>/ 或者 /root/)。
  - ~/configs:/etc/configs/:ro
  // 已经存在的命名的数据卷。
  - datavolume:/var/lib/mysql

对于/opt/data:/var/lib/mysql,前一个路径:/opt/data是宿主机目录,后一个路径:/var/lib/mysql是容器目录。

如果你不使用宿主机的路径,你可以指定一个volume_driver。

volume_driver: mydriver

11.5.17 volumes_from

从其它容器或者服务挂载数据卷,可选的参数是 :ro或者 :rw,前者表示容器只读,后者表示容器对数据卷是可读可写的。默认情况下是可读可写的。

volumes_from:
  - service_name
  - service_name:ro
  - container:container_name
  - container:container_name:rw

11.5.18 network_mode

网络模式,与Docker client的--net参数类似,只是相对多了一个service:[service name] 的格式。

例如:

network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"

11.5.19 networks

加入指定网络,格式如下:

services:
  some-service:
    networks:
     - some-network
     - other-network

关于这个标签还有一个特别的子标签aliases,这是一个用来设置服务别名的标签,例如:

services:
  some-service:
    networks:
      some-network:
        aliases:
         - alias1
         - alias3
      other-network:
        aliases:
         - alias2

相同的服务可以在不同的网络有不同的别名。

11.6 Docker Compose实战

这里就以Dcoerk Compose官网上计数器的例子(调用接口一次计数器+1)为例,services需要一个web app服务、redis服务和mysql服务。

11.6.1 编写计数器项目微服务

package com.example.dockercompose.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/dockerCompose")
public class HelloController {
    @Autowired
    StringRedisTemplate redisTemplate;

    @GetMapping("/hello")
    public String hello()
    {
        Long viewNum = redisTemplate.opsForValue().increment("views");
        return "hello, current view number is: " + viewNum;
    }
}

11.6.2 编写Dockerfile

FROM java:8
ENV MYPATH /opt/docker-compose
WORKDIR $MYPATH
ADD *.jar /docker-compose-app.jar
EXPOSE 18088
ENTRYPOINT ["java", "-jar", "/docker-compose-app.jar"]

11.6.3 编写docker-compose.yaml

version: '3.8'
services:
  docker-compose-app:
    build: .
    image: docker-compose-app
    depends_on:
      - redis
    ports: 
      - "8080:18088"
  redis:
    image: "library/redis:alpine"

项目目录如下:

img

11.6.4 部署环境

  • 将上面构建的jar包和文件拷贝到服务器的docker-compose目录下;
  • 打通端口。

服务器目录如下:

img

11.6.5 运行docker-compose

docker-compose up --build

结果:

img

img

11.6.6 测试

调用postman:

img

至此,使用docker-compose进行容器编排的demo演示完毕。

posted @   微笑带你去  阅读(141)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示