Docker学习笔记

一、简介

Docker是一个开源的应用容器引擎,它是由 Go 语言 开发实现的轻量级容器技术,它是基于 Linux 内核的 cgroupnamespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它隔离的进程,因此也称其为容器。

Docker 架构

二、概念

1. 概要理解

Docker支持安装各种软件并做好配置后编译成镜像,最终可以直接运行镜像产生一个或多个运行于宿主机内核上的容器。为了形象的理解容器,可以简单的理解它为运行在操作系统上的独立沙箱系统,这些沙箱系统内部的文件系统和Linux的非常相似,事实上确是如此,因为Docker就是融合了Linux内核而实现的虚拟化技术,由于具有沙箱的特性,所以各个容器之间是隔离运行的、独立的、互不影响的,程序员甚至不用担心容器内部损坏或者崩溃导致宿主机出错,因为只要删除掉这些有问题的容器,而再运行相关的镜像又会得到全新的容器了,只要不进行数据挂载,整个过程甚至干净不留残余。

2. 核心内容

docker主机(Host):或者称为docker宿主机,即安装了Docker程序的机器。

docker客户端(Client):连接docker主机进行操作的程序。

docker镜像(Images):是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

docker容器(Container):运行镜像后产生的实例称为容器,实质是运行于独立的 命名空间的进程。

docker仓库(Registry):集中存储、分发镜像的仓库服务系统,作用是允许用户上传、下载并管理镜像,包括公有仓库和私有仓库。

20180303165113

3. 对比传统虚拟机

特性 虚拟机的架构 容器的架构
启动 分钟级 秒级
性能 弱于原生 接近原生
硬盘使用 一般为GB 一般为MB
系统支持量 一般几十个 单机上千个容器

虚拟机的架构: 每个虚拟机都包括应用程序、必要的二进制文件和库以及一个完整的客户操作系统(Guest OS),尽管它们被分离,它们共享并利用主机的硬件资源,将近需要十几个 GB 的大小。

容器的架构: 容器包括应用程序及其所有的依赖,但与其他容器共享内核。它们以独立的用户空间进程形式运行在宿主机操作系统上。他们也不依赖于任何特定的基础设施,Docker 容器可以运行在任何计算机上,任何基础设施和任何云上。

4. 优势

(1)快速的启动时间

由于Docker容器直接运行于宿主内核,无需启动完整的操作系统即可运行,因此可以做到秒级、甚至毫秒级的启动时间,这大大的节约了开发、测试、部署的时间。

(2)一致的运行环境

开发过程中一个常见的问题是环境一致性问题。由于不同物理机的开发环境不一致,经常出现安装了相同的软件但却有不同的运行效果现象,甚至有的环境下还会出现bug。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,以至于不会再出现 “这段代码在我机器上没问题啊” 这类的问题。

(3)持续交付和部署

Docker可以一次创建或配置镜像,而可以在任意地方正常运行。即"一处构建,到处运行"。

(4)更方便的迁移

Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。

(5)更轻量的维护和扩展

Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的 官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。

5. 分层存储

因为镜像包含操作系统完整的 root 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 Union FS 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 ISO 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。

镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

6. 容器存储层

镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 容器存储层

7. 数据卷

数据卷 是一个可供一个或多个容器使用的特殊目录,当容器内部的目录(文件)映射到宿主机的某目录(文件)时,那么就称这个宿主机的目录(文件)为数据卷。它绕过 UFS,可以提供很多有用的特性,如下:

  • 数据卷 可以在容器之间共享和重用
  • 数据卷 的修改会立马生效
  • 数据卷 的更新,不会影响镜像
  • 数据卷 默认会一直存在,即使容器被删除

注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。

三、安装Docker

Docker 分为 CE 和 EE 两大版本。CE 即社区版,EE 即企业版,强调安全,付费使用。

1. 安装旧版本

centos7默认yum源头安装的Docker版本就是旧版本,旧版本的Docker称为 docker 或 docker-engine ,由于不在维护,故建议跳过直接安装新版本。

1
2
3
4
5
6
7
8
9
10
#检查内核版本,必须是3.10及以上
uname -r
#安装docker
yum install docker -y
#启动docker
systemctl start docker
#开机启动docker
systemctl enable docker
#查看docker版本,验证安装是否成功(有client和service两部分表示docker安装启动都成功了)
docker -v

2. 安装新版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#卸载旧版本,如果已安装这些程序,请卸载它们以及相关的依赖项
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

#安装新版docker yum源
yum install yum-utils -y
sudo yum-config-manager \
--add-repo \
https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo

#更新yum软件源缓存,并安装docker-ce。
sudo yum makecache fast
sudo yum update
sudo yum install docker-ce -y

#查看docker版本,验证安装是否成功(有client和service两部分表示docker安装启动都成功了)
docker version

#启动并加入开机启动
sudo systemctl start docker
sudo systemctl enable docker

更多内容:

查看所有仓库中所有docker版本:yum list docker-ce --showduplicates | sort -r

安装指定版本:比如sudo yum install docker-ce-17.12.0.ce

新版docker安装好后命令自动补全会有些缺失,所以可以选择安装命令补全依赖工具。

1
2
3
4
5
6
#docker自动补齐需要依赖工具bash-complete,安装好后会得到文件为 /usr/share/bash-completion/bash_completion
yum install -y bash-completion

#装好docker自动补齐依赖之后,要刷新下才能让其生效
source /usr/share/bash-completion/bash_completion
source /usr/share/bash-completion/completions/docker

3. 内网环境集群安装

(1)yum离线安装

如果内网中有一台服务器能够访问外网,可使用一下方式安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#在内网中可访问外网的服务器中下载清华的镜像源文件(要求这台服务器依软件赖环境纯净,最好没有安装过任何软件。目的是这台服务器的所有软件依赖一定要在其他内网服务器中都存在。)
$ wget -O /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo

$ sudo sed -i 's+download.docker.com+mirrors.tuna.tsinghua.edu.cn/docker-ce+' /etc/yum.repos.d/docker-ce.repo

$ yum update


$ sudo yum list docker-ce --showduplicates|sort -r

#下载docker安装的所有依赖到/tmp/docker-19.03目录
$ sudo yum install --downloadonly --downloaddir=/tmp/docker-19.03 docker-ce-19.03.8-3.el7 docker-ce-cli-19.03.8-3.el7

#复制下载好的/tmp/docker-19.03到目标服务器(比如192.168.0.13)
$ scp -r /tmp/docker-19.03/ root@192.168.0.13:/tmp/

#登录进目标服务器之后进入文件夹安装
$ ssh root@192.168.0.13
$ cd /tmp/docker-19.03/
$ yum install *.rpm
#启动并加入开机启动
$ sudo systemctl start docker
$ sudo systemctl enable docker

(2)完全离线安装

如果内网中所有服务器都不能访问外网,可使用一下方式安装:

下载安装包

在客户机中访问docker官网下载安装包:

安装包各版本下载地址:https://download.docker.com/linux/static/stable/x86_64/

这里下载的版本是:https://download.docker.com/linux/static/stable/x86_64/docker-19.03.5.tgz

安装运行

下载好安装包后通过ftp等客户端工具上传安装包到目标服务器,然后通过ssh客户端工具登录到该目标服务器执行如下命令:

1
2
3
4
5
6
7
8
9
10
#解压安装压缩包
$ tar xzvf docker-19.03.5.tgz
#拷贝安装
$ sudo cp docker/* /usr/bin/
#运行服务
$ dockerd &

#停止服务启动
$ ps -ef|grep dockerd
$ kill -9 1958 #这里的1958改成ps出来的pid

配置开机启动

1
2
3
4
5
6
#关闭selinux策略
$ vi /etc/selinux/config
SELINUX=disabled

#重启系统
$ reboot

配置docker.socket

1
$ vi /etc/systemd/system/docker.socket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[Unit]

Description=Docker Socket for the API

PartOf=docker.service

[Socket]

# If /var/run is not implemented as a symlink to /run, you may need to

# specify ListenStream=/var/run/docker.sock instead.

ListenStream=/run/docker.sock

SocketMode=0660

SocketUser=root

SocketGroup=docker

[Install]

WantedBy=sockets.target

配置docker.service

1
$ vi /etc/systemd/system/docker.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
[Unit]

Description=Docker Application Container Engine

Documentation=https://docs.docker.com

After=network-online.target firewalld.service

Wants=network-online.target

[Service]

Type=notify

# the default is not to use systemd for cgroups because the delegate issues still

# exists and systemd currently does not support the cgroup feature set required

# for containers run by docker

ExecStart=/usr/bin/dockerd

ExecReload=/bin/kill -s HUP $MAINPID

# Having non-zero Limit*s causes performance problems due to accounting overhead

# in the kernel. We recommend using cgroups to do container-local accounting.

LimitNOFILE=infinity

LimitNPROC=infinity

LimitCORE=infinity

# Uncomment TasksMax if your systemd version supports it.

# Only systemd 226 and above support this version.

#TasksMax=infinity

TimeoutStartSec=0

# set delegate yes so that systemd does not reset the cgroups of docker containers

Delegate=yes

# kill only the docker process, not all processes in the cgroup

KillMode=process

# restart the docker process if it exits prematurely

Restart=on-failure

StartLimitBurst=3

StartLimitInterval=60s

[Install]

WantedBy=multi-user.target

启动服务

1
2
3
$ systemctl daemon-reload
$ systemctl start docker
$ systemctl enable docker

4. 修改 Docker 的默认存储路径

Docker 默认安装的情况下,会使用 /var/lib/docker/ 目录作为存储目录,用以存放拉取的镜像和创建的容器等。不过由于此目录一般都位于系统盘,遇到系统盘比较小,而镜像和容器多了后就容易尴尬,这里说明一下如何修改 Docker 的存储目录。

1
2
3
4
#可通过如下命令查看Docker的默认存储路径
$ docker info
#Docker Root Dir就是Docker的存储路径
Docker Root Dir: /var/lib/docker

假设/data1目录挂载点的磁盘空间比较大,需要将Docker的存储路径改为改目录,则可通过如下操作修改:

1
$ vi /etc/docker/daemon.json
1
2
3
4
5
6
7
{
"registry-mirrors": [
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
],
"data-root": "/data1/docker/lib"
}
1
2
3
4
5
6
#然后重启 docker 服务
systemctl restart docker
#在执行就会看出存储路径已经修改了
docker info
#Docker Root Dir就是Docker的存储路径
Docker Root Dir: /data1/docker/lib

5. docker服务操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#启动
systemctl start docker
#开机自启动服务
systemctl enable docker

#重启服务
systemctl restart docker

#停止服务
systemctl stop docker
#如果以上无法停止服务,还需要关闭守护进程,报错提示:Warning: Stopping docker.service, but it can still be activated by: docker.socket
systemctl stop docker.socket
#禁止开机自启动服务
systemctl disable docker

6. 卸载 docker

1
2
3
4
#删除安装包
yum remove docker docker-ce
#删除镜像、容器、配置文件等内容
rm -rf /var/lib/docker

四、镜像加速

鉴于国内网络问题,Docker默认是从官方Docker Hub拉取 Docker 镜像十分缓慢,我们可以需要配置加速器来解决。

测速:https://github.com/docker-practice/docker-registry-cn-mirror-test/actions

编辑 /etc/docker/daemon.json(Linux) 或者 %programdata%\docker\config\daemon.json(Windows) 来配置 Daemon(如果文件不存在请新建该文件)。

1
$ vim /etc/docker/daemon.json

添加以下内容:

1
2
3
4
5
6
{
"registry-mirrors": [
"https://hub-mirror.c.163.com",
"https://docker.mirrors.ustc.edu.cn"
]
}

可以配置多个加速镜像。由于镜像服务可能出现宕机,建议同时配置多个加速镜像。

1
2
3
4
5
6
#重新加载配制
$ systemctl daemon-reload
#重启docker
$ service docker restart
#检查加速器是否生效
$ docker info

如果方便建议使用aliyun的镜像

地址:https://cr.console.aliyun.com/cn-shenzhen/instances/mirrors

image-20200216144500211

五、镜像操作

https://hub.docker.com/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#检索	(去https://hub.docker.com上查看镜像的详细信息。eg:docker search redis)
$ docker search 镜像关键字
#拉取 (name是镜像名,:tag是可选的,tag表示标签,多为软件的版本,默认是latest)
$ docker pull name[:tag]
#列出 (查看所有本地镜像)
$ docker images
#删除 (删除指定的本地镜像,image-id是镜像id)
$ docker rmi image-id

#查看容器/镜像详细信息
$ docker inspect image-name-or-id

#镜像导出
$ docker save -o 打包文件名.tar 镜像id
#或者
$ docker save 镜像id > 文件名.tar

#镜像导入
$ docker load -i 打包文件名.tar
#或者
$ docker load < 打包文件名.tar

镜像导出并导入案例:

1
2
3
4
5
6
#例如,以下导出方式将会让docker load导入时的镜像没有名字(即<none>)
$ docker save -o redis.tar 7864316753
#因此,推荐使用镜像的名字进行导出,此方式导入时就有镜像名称了
$ docker save -o redis:5.0.2
#另外,如果已经是<none>的名称了,也可以通过以下命令重命名镜像名称
$ docker tag [镜像id] [新镜像名称]:[新镜像标签]

六、容器操作

1. 操作命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#运行并创建一个容器
#-d表示守护态运行,即后台状态运行(可选)
#--name给容器自定义命名(可选)
#-p 端口映射(可选),映射单个端口:如-p 80:80 映射端口段:如-p 9000-9100:9000:9100
#-v 数据卷映射,即挂载物理主机文件/目录到容器里(可选)
docker run -d --name 容器名称 -p 主机端口:容器端口 -v 主机目录路径:容器目录路径 镜像名称

#关闭容器
docker stop 容器名称/容器id
#启动已经关掉的容器
docker start 容器名称/容器id
#重启容器
docker restart 容器名称/容器id

#删除指定容器
docker rm 容器名称/容器id
#强制删除当前所有容器
docker rm -f $(docker ps -a -q)

#容器列表(运行中)
docker ps
#容器列表(所有,包括停止的)
docker ps -a

#执行容器内命令
docker exec -it 容器名称或容器id 命令
#如:docker exec -it nginx ip iddr
#进入容器内部
docker exec -it 容器名称或容器id bash

#从主机拷贝文件到容器
docker cp 主机文件或目录 容器名称或容器id:容器文件或目录
#从容器拷贝文件到主机
docker cp 容器名称或容器id:容器文件或目录 主机文件或目录

#查看容器/镜像详细信息
docker inspect 容器名称/容器id
#查看容器运行日志
docker logs 容器名称/容器id
#查看容器资源的使用情况,包括:CPU、内存、网络 I/O 等
docker stats

#通过runlike去查看一个容器的docker run启动参数
# 安装runlike安装包
pip install runlike
# 查看容器运行命令
runlike -p <container_name> # 后面可以是容器名和容器id,-p参数是显示自动换行

#容器导出和导入
#将容器导出为镜像包
docker export -o 镜像包名称.tar 容器名称或容器id
#将镜像包导入为镜像
docker import 镜像包名称.tar 新镜像名称:新镜像标签

小案例:

1
2
$ docker pull hello-world
$ docker run hello-world

容器导出和导入案例:

1
2
3
4
5
6
7
8
9
10
#创建容器
docker run -itd --name centos7-test centos:centos7
#尝试修改容器
docker exec -it centos7-test touch /tmp/test
#将容器导出为镜像包
docker export -o centos7-test.tar centos7-test
#将镜像包导入为镜像
docker import centos7-test.tar centos7:centos7-test
#通过新镜像创建容器
docker run -itd --name centos7-test2 centos7:centos7-test

2. 时间同步问题

1
2
3
4
5
##为了保证容器和宿主机之间的时间同步
#1、使用docker run运行容器时,添加如下参数-v /etc/localtime:/etc/localtime:ro 如下:
docker run -it -v /etc/localtime:/etc/localtime:ro centos
#2、如果容器已经运行则可以如下操作:
docker cp -L /usr/share/zoneinfo/Asia/Shanghai [容器名]:/etc/localtime

七、数据管理

由于docker容器和宿主机系统是隔离的,这会带来下面几个问题:

  • 不能在宿主机上很方便地访问容器中的文件
  • 无法在多个容器之间共享数据
  • 当容器删除时,容器中产生的数据将丢失

为了能够保存(持久化)数据以及共享容器间的数据,docker 引入了数据卷(volume) 机制。数据卷是存在于一个或多个容器中的特定文件或文件夹,它可以绕过默认的联合文件系统,以正常的文件或者目录的形式存在于宿主机上。生存周期独立于容器的生存周期的,所以删除容器后数据卷并不会丢失。

容器中主要有两种管理数据方式:数据卷(Data Volumes)数据卷容器(Data Volume Containers)

1. 数据卷

数据卷是一个可供容器使用的特殊目录,它绕过文件系统,可以提供很多有用的特性:

(1)数据卷 可以在容器之间共享和重用

(2)对 数据卷 的修改会立马生效

(3)对 数据卷 的更新,不会影响镜像

(4)数据卷 默认会一直存在,即使容器被删除

数据卷的使用类似 linux 下对目录或文件进行 mount 操作,目前Docker提供了三种不同的方式将数据卷从宿主机挂载到容器中:

  • 数据卷挂载:volume mount
  • 绑定挂载:bind mount
  • tmpfs mount

其中volumebind mount比较常用,tmpfs mount基本不会用.

1.1 数据卷挂载

数据卷挂载即创建卷后挂载,数据卷挂载需要提前创建volume数据卷,然后再提供给容器绑定,新建的数据卷位于宿主机的/var/lib/docker/volumes目录里。这种挂载方式就是不需要手动指定宿主机目录并且能够通过docker volume命令来维护数据卷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#创建数据卷
docker volume create 数据卷名
#显示数据卷的详细信息
docker volume inspect 数据卷名
#列出所有的数据卷
docker volume ls
#删除未使用的数据卷(正被容器使用的数据卷无法删除)。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令
docker volume rm 数据卷名
#删除所有未使用的数据卷
docker volume prune

#运行容器并挂载数据卷
#type:挂载类型,包括volume(默认)和bind。source:数据卷名称(数据卷不存在则自动创建,如果不指定数据卷则自动创建随机数名称的数据卷)
#可以将多个数据卷挂载给一个容器;可以将一个数据卷挂载给多个容器,但是都共同使用同一个宿主机的数据卷目录
docker run -d --name 容器名称 --mount type=volume,source=数据卷名,target=容器内部目录或文件绝对路径 镜像名称或id
#可以使用-v(--volume)来简写,数据卷不存在则自动创建,如果不指定数据卷(如-v 容器内部目录或文件绝对路径)则自动创建随机数名称的数据卷
docker run -d --name 容器名称 -v 数据卷名:容器内部目录或文件绝对路径 镜像名称或id

##数据卷权限
#Docker挂载数据卷的默认权限是可读写rw,可以通过ro标记指定为只读,这样可以防止容器修改文件
#使用--mount时设置数据卷权限。不指定readonly则为读写
docker run -d --name 容器名称 --mount type=volume,source=数据卷名,target=容器内部目录或文件绝对路径,readonly 镜像名称或id
#使用-v(--volume)时设置数据卷权限
docker run -d --name 容器名称 -v 数据卷名:容器内部目录或文件绝对路径:rw 镜像名称或id
docker run -d --name 容器名称 -v 数据卷名:容器内部目录或文件绝对路径:ro 镜像名称或id

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
docker volume create my-vol
docker volume inspect my-vol
[
{
"CreatedAt": "2022-08-26T11:11:08+08:00",
"Driver": "local", #local表示宿主机。配置为vieux/sshfs(需要安装插件)可以把数据卷存储到其他云主机去,这里不做过多介绍。
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data", #数据卷所在宿主机位置
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]

docker run -d --name nginx --mount type=volume,source=my-vol,target=/usr/share/nginx/html nginx:1.22.0-alpine
#可以使用-v(--volume)来简写,如下:
#docker run -d --name nginx -v my-vol:/usr/share/nginx/html nginx:1.22.0-alpine

1.2 绑定挂载

绑定挂载即挂载时创建卷,使用绑定挂载的方式,用户可以自定义数据卷所在宿主机位置,对于用户来说这种方式有更好的自定义性,但是却不能使用docker volume命令来维护数据卷。

1
2
3
4
5
6
7
8
9
10
11
12
13
#使用source表示自定义数据卷路径,source必须是绝对路径,且路径必须已经存在,否则报错
docker run -d --name 容器名称 --mount type=bind,source=宿主机目录或文件绝对路径,target=容器内部目录或文件绝对路径 镜像名称或id
#可以使用-v(--volume)来简写,如下:
#--mount和-v(--volume)有个区别,就是使用-v宿主机目录或文件不存在也能自动创建,而使用--mount不存在则会运行失败
docker run -d --name 容器名称 -v 宿主机目录或文件绝对路径:容器内部目录或文件绝对路径 镜像名称或id

##数据卷权限
#Docker挂载数据卷的默认权限是可读写rw,可以通过ro标记指定为只读,这样可以防止容器修改文件
#使用--mount时设置数据卷权限。不指定readonly则为读写
docker run -d --name 容器名称 --mount type=bind,source=宿主机目录或文件绝对路径,target=容器内部目录或文件绝对路径,readonly 镜像名称或id
#使用-v(--volume)时设置数据卷权限
docker run -d --name 容器名称 -v 宿主机目录或文件绝对路径:容器内部目录或文件绝对路径:rw 镜像名称或id
docker run -d --name 容器名称 -v 宿主机目录或文件绝对路径:容器内部目录或文件绝对路径:ro 镜像名称或id

示例:

1
2
3
mkdir -p /opt/nginx
docker run -d --name nginx --mount type=bind,source=/opt/nginx,target=/usr/share/nginx/html nginx:1.22.0-alpine
#docker run -d --name nginx -v /opt/nginx:/usr/share/nginx/html nginx:1.22.0-alpine

2. 数据卷容器

数据卷不仅可以设置在宿主机,同样也可以设置在其他容器中。这样即使容器挂掉或者被删除,也不会影响数据卷容器里已经同步的数据,同样的数据卷容器挂掉或者被删除也不会影响容器的数据。

1
2
3
4
#创建数据卷容器
docker run -d -v 数据卷名称或者宿主机目录或文件:容器目录或文件 --name=数据卷容器名称 镜像名称[:版本号]或ID
#挂载数据卷容器给其他容器
docker run -d --name=容器名称 --volumes-from 数据卷容器名称或ID 镜像名称[:版本号]或ID

示例:

1
2
3
4
5
docker run -d --name nginx-backup  -v /opt/data:/opt/data nginx:1.22.0-alpine
docker run -d --name nginx --volumes-from nginx-backup nginx:1.22.0-alpine
docker exec -it nginx touch /opt/data/test.txt
docker rm -f nginx
docker exec -it nginx-backup ls /opt/data

八、docker网络

Docker默认提供了3种网络模式,生成容器时不指定网络模式下默认使用bridge桥接模式。

1. host模式

容器不会创建自己的网卡,配置 IP 等,而是使用宿主机的 IP 和端口

1
docker run -d --name 容器名称 --net=host   镜像名称

2. container模式

容器不会创建自己的网卡,配置 IP 等,而是和一个指定的容器共享 IP和端口

1
docker run -d --name 容器名称 --net=container:容器名称或容器id   镜像名称

3. none模式

关闭网络功能,不进行任何网络设置

1
docker run -d --name 容器名称 --net=none   镜像名称

4. bridge模式(默认模式)

为每一个容器分配、设置 IP 等,并将容器连接到 docker0 虚拟网桥上,这是 docker run 创建容器时使用的默认模式。

当Docker进程启动时,会在主机上创建一个名为docker0的虚拟网桥(网卡/路由器),此主机上启动的Docker容器都会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。

宿主机和Docker容器之间是可以进行网络连接的,同样的,Docker容器和容器之间只要是在一个虚拟网桥下,那么也可以直接进行网络连接。

1
2
#通过--net=bridge显示指定,默认就是bridge,也可以不设置
docker run -d --name 容器名称 --net=bridge 镜像名称

5. 自定义网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
##自定义创建名称为mynet的网络
#--driver bridge 桥接模式
#--subnet 192.168.0.0/16 子网掩码(网段) 范围192.168.0.2-192.168.255.255
#--gateway 192.168.0.1 网关
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet

#删除网络
docker network rm mynet
#删除主机上全部未使用的网络
docker network prune

#查看当前Docker所有的网络模式
docker network ls

#查看自定义网络详细信息
docker network inspect mynet

#通过--net在启动容器时指定自定义网络
docker run -d --name 容器名称 --net mynet 镜像名称

#不同Docker网络之间的容器想要连接的话,需要把该容器注册到另一个容器所在的网络上
#如下假设centos01容器本来在默认的bridge(docker0)上,想要让其和mynet网络下的容器互通,则需要把centos01注册到mynet中。
#成功注册到mynet网络后,docker将会在centos01中创建一个网卡并且分配一个mynet网段内的IP,并且将新ip-主机名映射加入自己的hosts中
docker network connect mynet centos01
  • 默认docker0是无法Ping通容器名,而自定义网络可以Ping通容器名。
  • 在多个微服务部署到docker时,通过自定义网络方便统一的网络配置。

6. 多个容器之间通过容器名称互相访问

docker run 创建容器时默认使用的是 docker0 网络,容器可以通过另一个容器的 ip 来访问其他容器主机,但是没法通过容器 id 或者主机名来访问,因为容器的 hosts 文件中默认只有容器自己的 ip-主机名映射,而没有其他容器的。为了能够通过容器名称来访问其他容器,可以在容器的 hosts 文件中配置其他容器的 ip-主机名映射,这个过程可以使用 docker exec 命令进入容器内手动修改 hosts 文件实现,但这种方式会有点麻烦。还有另一解决方式是在 docker run 创建容器的时候通过指定 --link 参数来配置其他容器的ip-主机名映射。如下:

1
2
#创建centos02容器,并在其内部hosts中加入centos01容器的ip-主机名映射。
docker run -d -it --name centos02 --link centos01容器的 centos:centos7

docker run 创建容器时会自动把分配到的的 ip 和 hostname(即主机名,默认为容器id)写入到容器的hosts文件中。其中 主机名可以在 docker run 时指定 --hostname 来修改。

如上配置后就能让 centos02 能够访问到 centos01,但是 centos01 却不能访问 centos02,这是因为 centos01 的hosts 文件中没有加入 centos02 的 ip - hostname映射,除非使用 docker exec 命令进入 centos01 容器内修改其 hosts 文件,可见使用--link 参数配置的方式操作也很麻烦。

为了更加简便地实现 docker 中多个容器之间的互相访问,有一种更好的解决方案,那就是使用“docker自定义网络”,docker 自定义网络可以理解为“网卡”,docker 允许用户将多个容器配置到同一个自定义网络中,容器之间通过容器名称互相访问的时候,docker 将会根据自定义网络中的容器配置信息解析容器名称得到对应的 ip ,进而实现访问。

可以通过 docker inspect 自定义网络名称 命令来查看自定义网络中的容器配置信息,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
docker network ls
#查看自定义网络配置
docker inspect 自定义网络名称
#自定义网络中多个容器的配置信息如下:
[
{
"Name": "mynet",
"Containers": {
"0a348b84901337546e60bfcc7cd8592a81e244339337f1ceea9b578bba09d9dd": {
"Name": "nginx2",
"EndpointID": "867176b7354f17095eff8f5a047bbae062d3f80b4ad150f1711a13f0543ed549",
"MacAddress": "02:42:c0:a8:00:04",
"IPv4Address": "192.168.0.4/16",
"IPv6Address": ""
},
"6bfc4ad47e7f878f14b5fa1c3a59029fc6b50a1e04cdb946bd45de4e052a7416": {
"Name": "nginx1",
"EndpointID": "4498313ad280f9e15c86f5c756a66dbdb39982f8469be50c2761e058b333bb31",
"MacAddress": "02:42:c0:a8:00:03",
"IPv4Address": "192.168.0.3/16",
"IPv6Address": ""
},
"a29f5b6558b22bf557a2b997c3c03284f013bc75da89065a983a2452f27bee0c": {
"Name": "nginx3",
"EndpointID": "85e61dc0d27a7d2a7be14c11e6566656817693981ee22b277328ee3116aafef4",
"MacAddress": "02:42:c0:a8:00:02",
"IPv4Address": "192.168.0.2/16",
"IPv6Address": ""
}
}
}
]

将多个容器配置到同一个自定义网络中的操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#创建一个名称为mynet的自定义网络
docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 mynet
#使用默认bridge(docker0)网络创建centos01
docker run -d --name centos01 centos:centos7
#使用自定义网络mynet创建centos02
docker run -d --name centos02 --net mynet centos:centos7
docker run -d --name centos01 centos:centos7
#使用自定义网络mynet创建centos03
docker run -d --name centos03 --net mynet centos:centos7
#测试centos01是否连通到centos03情况(发现连通成功)
docker exec -it centos01 ping centos03
#测试centos03是否连通到centos01情况(发现连通成功)
docker exec -it centos03 ping centos01
#测试centos01是否连通到centos02情况(发现不通)
docker exec -it centos01 ping centos02
#把该centos01注册到centos02所在的mynet网络上。
docker network connect mynet centos01
#测试centos01是否连通到centos02情况(发现连通成功)
docker exec -it centos01 ping centos02

7. 查看容器ip

两种方式:

1、由于docker run创建容器时就会给容器分配ip等信息了,所以可以直接使用docker inspect命令来过滤数据获取。

2、通过docker exec命令进入容器内部在使用如ifconfig等方式查看

1
2
3
4
5
6
7
8
9
10
##通过docker inspect查看容器ip
#查看容器ip
docker inspect --format='{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 容器名称或容器id
#显示所有容器 IP 地址
docker inspect --format='{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -aq)

##通过docker exec查看容器ip
docker exec -it 容器名称或容器id ifconfig
docker exec -it 容器名称或容器id ip iddr
docker exec -it 容器名称或容器id cat /etc/hosts

参考:https://blog.csdn.net/CSDN1csdn1/article/details/123961079

8. docker与防火墙

8.1 docker网络中防火墙的配置

1)bridge 网络模式的防火墙配置

Docker 默认使用的是 bridge 网络模式,在创建容器时会自动为容器配置 iptables 规则以进行网络安全控制。具体来说,Docker 会在主机上创建一个名为 docker0 的虚拟网桥,并将容器连接到该网桥中。同时,它还会为每个容器分配一个随机的 IP 地址,并通过 NAT 的方式将容器和宿主机的 IP 地址进行映射。

说明:

bridge 网络模式下,启动或停止 docker 容器时,docker 都会在 iptables 防火墙配置中自动创建或删除和容器相关的规则策略,而不需要人为手动进行配置。

由于 docker 使用的是iptables 命令维护防火墙配置,所以用户只能通过iptables 命令来查看 docker 相关的防火墙规则策略,而使用firewallufw 等上层防火墙工具则无法查看。

2)host 网络模式的防火墙配置

当 Docker 使用 host 网络模式时,Docker 容器将直接使用宿主机的网络环境,包括 IP 和端口。此时 Docker 不会再为容器自动进行 iptables 配置,而是直接使用宿主机的防火墙规则。

8.2 限制外网访问 docker 容器

限制外网访问 docker 容器是常见运维需求,例如:

  • (1)多个 docker 微服务容器之间可以在内网中互相访问,但不允许外网直接访问。
  • (2) docker 中的 HTTP 服务容器未进行 SSL 加密而直接提供外网访问是非常不安全的,一般做法是先配置 docker 容器禁止外网访问而只允许内网访问,再使用 nginx 通过反向代理转发外网的 HTTPS 请求给 docker 容器中的 HTTP 服务。

要实现这类运维需求,有很多解决方式,常见的方式如下:

1)限制外网访问 docker 容器

方式1:使用默认的 bridge 网络模式创建 docker 容器并指定端口映射时限制访问 ip。操作如下:

1
docker run -d --name 容器名称 -p 127.0.0.1:宿主机端口:容器端口  镜像名称

注意:docker会自动维护防火墙配置,通常不设置 ip 则容器对应的宿主机端口默认对所有 IP 访问不限制。

这种方式运行容器,容器程序的监听端口会占用宿主机的端口。

方式2:使用 host 网络模式创建 docker 容器,让容器监听端口直接使用宿主机端口。操作如下:

1
docker run -d --name 容器名称 --net=host  镜像名称

注意:使用 host 网络模式创建 docker 容器时, docker 不会进行 iptables 自动配置,而是直接使用宿主机的防火墙规则。

这种方式运行容器,容器程序的监听端口会占用宿主机的端口。

方式3:使用默认的 bridge 网络模式创建 docker 容器但不进行端口映射,这种情况宿主机或其他容器可通过容器的 ip 进行访问

1
2
3
4
5
6
7
8
9
10
11
#未设置固定IP运行容器,则docker会根据默认的bridge网络ip网段分配一个随机ip
docker run -d --name 容器名称 镜像名称

#使用固定IP运行容器(默认的bridge网络ip网段为172.17.0.0/16,可以手动指定在这个网段内的容器固定ip)
docker run -d --name 容器名称 --ip 172.17.0.100 镜像名称


#查看容器的ip
docker inspect 容器名称或ID | grep IPAddress
#查看默认的bridge网络的ip网段
docker network inspect bridge | grep Subnet

这种方式运行容器,容器程序的监听端口不会占用宿主机的端口。

2)通过nginx反向代理到docker的HTTP服务容器

(1)配置 nginx SSL证书和反向代理,如下:

配置nginx SSL证书并监听一个对外网访问的端口,并反向代理到docker容器的监听端口(设端口为:8080)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 4433 ssl; #此处端口为nginx对外网暴露的端口
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
charset utf-8;

location ^~ / {
proxy_pass http://127.0.0.1:8080; #此处端口为docker的HTTP服务容器的监听端口
#proxy_pass http://172.17.0.100:8080;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 100m;
}
}
  • 假设docker run 运行 http 容器时限制 ip 映射端口为 -p 127.0.0.1:宿主机端口:容器端口,那么可以代理到 http://127.0.0.1:8080
  • 而如果不进行端口映射,则可以通过命令 docker inspect 容器名称或ID | grep IPAddress 查看容器的 ip ,然后反向代理到对应的地址,如:http://172.17.0.100:8080

(2)配置防火墙放行nginx对外网暴露的端口

1
2
3
4
5
6
7
8
9
10
##宿主机配置防火墙
#firewall防火墙配置(Redhat系列Linux)
firewall-cmd --permanent --add-port=4433/tcp #创建规则
#firewall-cmd --permanent --remove-port=4433/tcp #删除规则
firewall-cmd --reload #重载配置生效

#ufw防火墙配置(Debain系列Linux)
ufw allow 4433 #创建规则
#ufw delete allow 4433 #删除规则
ufw reload #重载配置生效

九、构建自定义镜像

方式1:上传程序到vps后构建

需求:构建一个能在tomcat容器里运行的war包程序镜像,并通过镜像创建一个实例容器。

由于依赖于tomcat容器,故需要先拉去tomcat镜像到本地。tomcat镜像主页:https://c.163yun.com/hub#/library/repository/info?repoId=3105

(1)拉取tomcat镜像到宿主机

1
$ docker pull hub.c.163.com/library/tomcat:latest

(2)上传war包

创建一个webdemo项目,并在这个webdemo项目的资源路径里添加一个index.jsp页面。

1
2
3
4
5
<html>
<body>
<h2>Hello World!</h2>
</body>
</html>

然后打包得到webdemo.war文件。并上传这个webdemo.war文件到vps的/root/project目录

(3)编写Dockerfile文件

1
2
#在/root/project下编写一个Dockerfile文件
$ vim Dockerfile
1
2
3
4
5
6
#继承于某个镜像
from hub.c.163.com/library/tomcat
#镜像所有者信息
MAINTAINER qcmoke qcmoke@gmail.com
#把程序copy到容器镜像当中,前面的参数是本地路径,后面的参数是容器镜像路径(可以到镜像主页得到tomcat的webapp路径)
COPY /root/project /usr/local/tomcat/webapps

(4)构建生成镜像

1
2
#在Dockerfile文件所在的目录,即/root/project里执行如下命令
$ docker build -t webdemo:latest .

(5)查看本地镜像仓库是否构建成功

1
$ docker images
1
2
3
REPOSITORY                     TAG                 IMAGE ID            CREATED                  SIZE
docker.io/tomcat latest aeea3708743f Less than a second ago 529 MB
webdemo latest 10f75818641a 46 seconds ago 310 MB

(6)通过构建的镜像实例化一个容器并运行

1
$ docker run -d -p 8080:8080 webdemo:latest

(7)访问测试

访问index.jsp:http://192.168.222.131:8080/webdemo/index.jsp

image-20200215111413522

(8)其他

在用-d指定为后台启动的情况下,可以使用以下命令查看容器实时的日志情况:

1
$ docker logs -f 容器名称或者容器id

方式2:通过idea插件连接vps构建

(1)配置docker允许外网访问

在vps中配置docker允许外网访问

1
$ vim /usr/lib/systemd/system/docker.service

ExecStart属性原来值的最后追加

1
-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock

新版Docker CE配置如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
....
....
[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
....
....

旧版Docker配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Service]
....
....
ExecStart=/usr/bin/dockerd-current \
--add-runtime docker-runc=/usr/libexec/docker/docker-runc-current \
--default-runtime=docker-runc \
--exec-opt native.cgroupdriver=systemd \
--userland-proxy-path=/usr/libexec/docker/docker-proxy-current \
--init-path=/usr/libexec/docker/docker-init-current \
--seccomp-profile=/etc/docker/seccomp.json \
$OPTIONS \
$DOCKER_STORAGE_OPTIONS \
$DOCKER_NETWORK_OPTIONS \
$ADD_REGISTRY \
$BLOCK_REGISTRY \
$INSECURE_REGISTRY \
$REGISTRIES \
-H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock
....
....

(2)重新加载配置并重启Docker

1
2
$ systemctl daemon-reload
$ systemctl restart docker

(3)启动防火墙并开放2375端口

开放了防火墙2375端口用于远程连接

1
2
3
4
$ service firewalld start
$ firewall-cmd --permanent --add-port=2375/tcp
$ firewall-cmd --reload
$ firewall-cmd --list-all
  • 不需要另外开放容器映射到宿主机的端口。
  • 经操作发现,如果使用vmware的linux作为vps,不能直接关闭防火墙,开放相应的端口即可。

(4)创建一个springboot项目

image-20200216133115274

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo-boot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--将其他模块的依赖一起打包生成可执行jar-->
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

DemoApplication.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author qcmoke
*/
@RestController
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}

@RequestMapping("/hello")
public Object hello() {
return "hello docker!";
}
}

application.yml

1
2
server:
port: 8080

Dockerfile

1
2
3
4
5
6
7
8
9
FROM openjdk:8u212-jre
#存放持久化数据的目录
VOLUME /tmp
#要复制到容器中的问件
ADD target/demo-boot-0.0.1-SNAPSHOT.jar /demo-boot.jar
#docker容器启动时执行的命令
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/demo-boot.jar"]
#启动端口号(容器中的)
EXPOSE 8080

(5)编译项目生成jar包

image-20200216134810561

(6)配置远程docker插件连接

默认新版的idea自带有这个插件,如果没有直接到插件中心安装即可。

image-20200216133850397

(7)然后配置DockerFile

image-20200216132351344

image-20200216134320278

(8)运行docker插件

运行插件部署镜像到vps,并通过镜像创建一个实例容器

image-20200216140432811

(9)访问测试

最后访问: http://192.168.222.132:8080/hello

image-20200216141102237

十、docker compose

1. 简介

Compose项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排。

地址:https://github.com/docker/compose

使用一个 Dockerfile 模板文件,可以让用户很方便的定义一个单独的应用容器。然而,在日常工作中,经常会碰到需要多个容器相互配合来完成某项任务的情况。Compose 允许用户通过一个单独的 docker-compose.yml 模板文件来定义一组相关联的应用容器为一个项目。

2. 安装

在 Linux 上的也安装十分简单,从 官方 GitHub Release 处直接下载编译好的二进制文件即可。

例如,在 Linux 64 位系统上直接下载对应的二进制包。

1
2
3
$ sudo curl -L https://github.com/docker/compose/releases/download/1.24.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version

因为 Docker Compose 存放在 GitHub,可能网络不太稳定。可以想办法下载后再上传到服务器,操作如下:

下载地址:https://github.com/docker/compose/releases/download/1.24.1/docker-compose-Linux-x86_64

1
>mv docker-compose-Linux-x86_64 /usr/local/bin/docker-compose

3. 卸载

如果是二进制包方式安装的,删除二进制文件即可。

1
$ sudo rm /usr/local/bin/docker-compose

4. 编排文件

在任意目录创建compose编排配置文件:docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
## docker-compose.yml文件的版本
version: '3.1'
services: # compose管理的服务
redis:
image: redis # 指定镜像
container_name: redis-1 # 容器名称
restart: always # 开机自动启动
ports: # 端口映射
- 6379:6379
volumes: # 目录映射
- /micro-cloud-erp/redis/conf:/usr/local/etc/redis
- /micro-cloud-erp/redis/data:/data
command: redis-server --requirepass qcmoke123456 --appendonly yes #执行的命令; requirepass来指定远程登录密码; appendonly来指定是否开启持久化策略
#command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes

mysql:
image: mysql:5.7.24
container_name: mysql-1
ports:
- 3306:3306
environment: # 环境变量
MYSQL_ROOT_PASSWORD: qcmoke123456 # 设置数据库root密码
restart: always
volumes:
- /micro-cloud-erp/mysql/data:/var/lib/mysql
#当 MySQL 服务启动时会以/etc/mysql/my.cnf为配置文件,而该文件会导入/etc/mysql/conf.d目录中所有以.cnf为后缀的文件。
#这些文件会拓展或覆盖 /etc/mysql/my.cnf 文件中的配置。因此创建以.cnf结尾的配置文件并挂载至MySQL容器中的/etc/mysql/conf.d目录即可。
#在本项目中只要在docker-compose up前上传my.cnf文件到宿主机的/micro-cloud-erp/mysql/conf.d/my.cnf目录即可。如果在docker-compose up后的话,需要重启mysql容器才能重新加载到自定义的配置文件。
- /micro-cloud-erp/mysql/conf.d:/etc/mysql/conf.d

nacos:
image: nacos/nacos-server
container_name: nacos-standalone
environment:
- MODE=standalone
ports:
- 8848:8848

下面是compose编排配置文件中使用到的mysql配置文件:my.cnf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8

[mysqld]
# 设置3306端口
port = 3306
# 设置mysql的安装目录
# basedir=/var/lib/mysql
# 设置 mysql数据库的数据的存放目录,如果是MySQL 8+ 不需要以下配置,系统自己生成即可,否则有可能报错
# datadir=/var/lib/mysql/data
# 允许最大连接数
max_connections=20
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 最大连接数
max_connections=500

5. 操作命令

docker-compose.yml所在的目录执行如下命令

1
2
3
4
5
6
7
8
#尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。相关的服务都将会被自动启动,除非已经处于运行状态。
$ docker-compose up
#以守护进程模式运行加-d选项
$ docker-compose up -d
#此命令将会停止 up 命令所启动的容器,并移除网络
$ docker-compose down
#重启项目中的服务。
$ docker-compose restart [options] [SERVICE...]

十一、Docker Swarm

Docker Swarm 是 Docker 的集群管理工具,或者说是资源管理工具。swarm 集群由管理节点(manager)和工作节点(work node)构成。

swarm mananger:负责整个集群的管理工作包括集群配置、服务管理等所有跟集群有关的工作。

work node:即图中的 available node,主要负责运行相应的服务来执行任务(task)。

注意:跟集群管理有关的任何操作,都是在管理节点上操作的。

准备6台linux服务器

manager-1:192.168.99.101

manager-2:192.168.99.102

manager-3:192.168.99.103

worker-1:192.168.99.201

worker-2:192.168.99.202

worker-3:192.168.99.203

1. 关闭所有服务器的防火墙

1
$ service firewalld stop

2. 初始化 swarm 集群

进行初始化的这台机器,就是集群的管理节点。

1
$ docker swarm init --advertise-addr 192.168.68.101

eg:

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# docker swarm init --advertise-addr 192.168.68.101
Swarm initialized: current node (w7xdf6see52t2t24x1ykedg5o) is now a manager.

To add a worker to this swarm, run the following command:

docker swarm join \
--token SWMTKN-1-04sl6nb0acbzzgvfrqycph4jtnyh0wcoix70gz44sjuaf66cwd-3xqtzzxymio3077xfki41od0e \
192.168.68.101:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

3. 添加worker节点

在manager节点下生成worker令牌,以让其他worker服务器加入

1
$ docker swarm join-token worker

eg:

1
2
3
4
5
6
[root@localhost ~]# docker swarm join-token worker
To add a worker to this swarm, run the following command:

docker swarm join \
--token SWMTKN-1-04sl6nb0acbzzgvfrqycph4jtnyh0wcoix70gz44sjuaf66cwd-3xqtzzxymio3077xfki41od0e \
192.168.68.101:2377

然后在三台worker节点运行:

1
2
3
$ docker swarm join \
--token SWMTKN-1-04sl6nb0acbzzgvfrqycph4jtnyh0wcoix70gz44sjuaf66cwd-3xqtzzxymio3077xfki41od0e \
192.168.68.101:2377

4. 添加manager节点

在manager节点下生成manager令牌,以让其他manager服务器加入

1
$ docker swarm join-token manager

eg:

1
2
3
4
5
6
[root@localhost ~]# docker swarm join-token manager
To add a manager to this swarm, run the following command:

docker swarm join \
--token SWMTKN-1-04sl6nb0acbzzgvfrqycph4jtnyh0wcoix70gz44sjuaf66cwd-4nbcolan6w4akrs3zsbvrkqxe \
192.168.68.101:2377

然后在三台manager节点运行如下命令:

1
2
3
$ docker swarm join \
--token SWMTKN-1-04sl6nb0acbzzgvfrqycph4jtnyh0wcoix70gz44sjuaf66cwd-4nbcolan6w4akrs3zsbvrkqxe \
192.168.68.101:2377

5. 查看所有管理的节点

manager-1运行命令查看所有管理的节点

1
$ docker node ls

eg:

1
2
3
4
5
6
7
8
[root@localhost ~]# docker node ls
ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS
dif8w8hp6pevz9k0owqkg6hjl localhost.localdomain Ready Active
j9jqdioy91olyxd8dfghgm8qc localhost.localdomain Ready Active
mfv24dim85bi7sd9wl589rhdf localhost.localdomain Ready Active Reachable
q1qayx19ebfg7oi8dv7e3mzyh localhost.localdomain Ready Active
rbeb5mykx83qpr6u8afriqx9z localhost.localdomain Ready Active Reachable
w7xdf6see52t2t24x1ykedg5o * localhost.localdomain Ready Active Leader

6. 创建集群服务

1
$ docker service create -p 80:80 --name my-nginx nginx

这时候将会在任意节点中选择一台通过docker创建nginx服务。我们可以在集群下任意一台服务器(manager或者worker节点)访问nginx服务都能访问到,因为swarm创建的服务使用的是swarm集群网络。

7. 查看服务集群情况

1
$ docker service ls

eg:

1
2
3
[root@localhost ~]# docker service ls
ID NAME MODE REPLICAS IMAGE
v1hn945nmyds my-nginx replicated 1/1 nginx:latest

REPLICAS:副本数量

8. 集群服务扩容

对my-nginx服务扩容为5个

第一种方式:

1
$ docker service update --replicas 5 my-nginx

eg:

1
2
3
4
5
[root@localhost ~]# docker service update --replicas 5 my-nginx
my-nginx
[root@localhost ~]# docker service ls
ID NAME MODE REPLICAS IMAGE
v1hn945nmyds my-nginx replicated 5/5 nginx:latest

第二种方式:

1
$ docker service scale my-nginx=5
1
2
3
4
5
[root@localhost ~]# docker service scale my-nginx=5
my-nginx scaled to 5
[root@localhost ~]# docker service ls
ID NAME MODE REPLICAS IMAGE
v1hn945nmyds my-nginx replicated 5/5 nginx:latest

update更多的应用场景在于image的升级导致的运行中的服务需要更新等等

9. 删除集群服务

1
$ docker service rm my-nginx

这时候所有副本都会被删除

10. 修改服务

比如修改nginx服务镜像版本为1.9.5

1
$ docker service update --image nginx:1.9.5 my-nginx
1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost ~]# docker service ps my-nginx
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
kzg8qncaal9a my-nginx.1 nginx:1.9.5 localhost.localdomain Running Running about a minute ago
er55pvmd8fn6 \_ my-nginx.1 nginx:latest localhost.localdomain Shutdown Shutdown about a minute ago
kv3t6poxh3rb my-nginx.2 nginx:1.9.5 localhost.localdomain Running Running about a minute ago
tuzkxmb0th6x \_ my-nginx.2 nginx:latest localhost.localdomain Shutdown Shutdown about a minute ago
edt0tg6tg120 my-nginx.3 nginx:1.9.5 localhost.localdomain Running Running about a minute ago
qfwlzj8hb3gl \_ my-nginx.3 nginx:latest localhost.localdomain Shutdown Shutdown about a minute ago
bb9kjj3n81yx my-nginx.4 nginx:1.9.5 localhost.localdomain Running Running about a minute ago
spx8ybz9wlph \_ my-nginx.4 nginx:latest localhost.localdomain Shutdown Shutdown about a minute ago
vs4akco7zpls my-nginx.5 nginx:1.9.5 localhost.localdomain Running Running about a minute ago
qtaqq0jout6k \_ my-nginx.5 nginx:latest localhost.localdomain Shutdown Shutdown about a minute ago

11. 服务回滚

1
$ docker service update --rollback

十二、常用docker镜像安装综合案例

步骤:软件镜像(安装程序)—> 运行镜像 —> 产生一个容器(正在运行的软件)。

1. 安装nginx

1
2
3
4
5
6
7
8
9
10
11
12
#拉取(下载)镜像
$ docker pull hub.c.163.com/library/nginx:latest

#根据镜像实例化一个容器并运行,运行好后访问http://192.168.222.131:8080
$ docker run -d --name nginx1 -p 8080:80 nginx

#可以进入容器内部执行命令(里面的文件系统和原生linux的一样)
$ docker exec -it nginx1 bash
#在容器里执行命令查看nginx命令的位置
$ which nginx
#退出容器
$ exit

2. 安装tomcat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#1、搜索镜像
$ docker search tomcat

#2、拉取(下载)镜像
#$ docker pull tomcat
#这里使用的是网易镜像站的,网址:https://c.163yun.com/hub#/home
$ docker pull hub.c.163.com/library/tomcat

#3、根据镜像实例化一个容器并运行
# 选项:-d:表示后台运行;-p: 表示将主机的端口映射到容器的一个端口,如-p 主机端口:容器内部的端口。
# 启动一个做了端口映射的tomcat,假设虚拟机ip为192.168.222.132,那么启动容器后访问http://192.168.222.132:8080
$ docker run -d --name mytomcat -p 8080:8080 hub.c.163.com/library/tomcat

#4、查看运行中的容器
$ docker ps

#5、查看当前所有容器
$ docker ps -a

#6、 停止运行中的容器
$ docker stop mytomcat

#7、启动容器
$ docker start mytomcat

#8、删除一个容器(需要先停止运行中的容器)
$ docker rm mytomcat

#9、查看容器的日志 docker logs container-name或者container-id
$ docker logs mytomcat

更多命令参看https://docs.docker.com/engine/reference/commandline/docker/可以参考每一个镜像的文档

3. 安装mysql

(1)获取镜像

1
2
docker pull mysql
#docker pull mysql:5.7 #指定版本用 :标签(版本号)

(2)启动镜像

1
2
3
4
5
6
#简单方式
$ docker run --name mysql01 -e MYSQL_ROOT_PASSWORD=123456 -d mysql

#做端口映射的启动方式:
$ docker run -p 3306:3306 --name mysql02 -e MYSQL_ROOT_PASSWORD=123456 -d mysql
#docker run -p 3306:3306 --name mysql5_7 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7

几个其他的高级操作

1
2
3
4
5
$ docker run --name mysql03 -v /opt/mysql:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag
#指定mysql的一些配置参数
#docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
#把mysqldocker容器的/opt/mysql/conf.d目录挂载到宿主机的/conf/mysql目录
#改mysql的配置文件就只需要把mysql配置文件放在自定义的文件夹下(/opt/mysql)

mysql完整案例

1
2
3
4
$ docker pull mysql:5.7
$ mkdir /opt/mysql/conf -p
$ mkdir /opt/mysql/data -p
$ vim /opt/mysql/conf/my.cnf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
## my.cnf
[mysql]
# 设置mysql客户端默认字符集
default-character-set=utf8

[mysqld]
# 设置3306端口
port = 3306
# 设置mysql的安装目录
# basedir=/var/lib/mysql
# 设置 mysql数据库的数据的存放目录,如果是MySQL 8+ 不需要以下配置,系统自己生成即可,否则有可能报错
datadir=/var/lib/mysql/data
# 允许最大连接数
max_connections=20
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
# 最大连接数
max_connections=500
1
2
3
#运行mysql实例
$ docker run -d --name mysql -p 3306:3306 -v /opt/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
$ docker run -d --name mysql -p 3306:3306 -v /opt/mysql/conf:/etc/mysql/conf.d -v /opt/mysql/data:/var/lib/mysql/data -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7

4. 安装rabbitmq

(1)获取镜像

1
2
#该版本包含了web控制页面
docker pull rabbitmq:management

(2)运行镜像

1
2
3
4
5
#方式一:默认用户名和密码都是guest
docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5672:5672 rabbitmq:management

#方式二:设置用户名和密码
docker run -d --hostname my-rabbit --name rabbit -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=password -p 15672:15672 -p 5672:5672 rabbitmq:management

(3)访问rabbitmq管理页面页面 http://ip:15672

5. 安装redis

创建redis配置文件

1
2
$ mkdir /opt/redis/conf/  /opt/redis/data/ -p
$ vim /opt/redis/conf/redis.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#节点端口
port 6379
#访问认证密码
requirepass 123456
#如果主节点开启了访问认证,从节点访问主节点需要认证的密码
masterauth 123456
#日志文件和序列化文件等默认存当前目录,可修改到指定目录
#dir ./
#保护模式,默认值 yes,即开启。开启保护模式以后,需配置 bind ip 或者设置访问密码;关闭保护模式,外部网络可以直接访问;
protected-mode no
#是否以守护线程的方式启动(后台启动),默认 no;
daemonize no
#是否开启 AOF 持久化模式,默认 no;
appendonly yes
#是否开启集群模式,默认 no;
cluster-enabled no

该原始文件可从官网下载

bind 127.0.0.1 #注释掉这部分,这是限制redis只能本地访问

protected-mode no #默认yes,开启保护模式,限制为本地访问

daemonize no#默认no,改为yes意为以守护进程方式启动,可后台运行,除非kill进程,改为yes会使配置文件方式启动redis失败

databases 16 #数据库个数(可选),我修改了这个只是查看是否生效。。

dir ./ #输入本地redis数据库存放文件夹(可选)

appendonly yes #redis持久化(可选)

requirepass 密码 #配置redis访问密码

运行redis镜像

1
docker run -p 6379:6379 --name redis -v /opt/redis/conf/redis.conf:/etc/redis/redis.conf -v /opt/redis/data:/data -d redis:6.2.7  redis-server /etc/redis/redis.conf --appendonly yes

配置防火墙

1
2
3
$ firewall-cmd --permanent --add-port=6379/tcp
$ firewall-cmd --reload
$ firewall-cmd --list-all

6. 安装zookeeper

1
2
3
4
#-Xmx指定应用程能够使用的最大内存数
$ docker run -d --name zookeeper --restart always -e JVMFLAGS="-Xmx1024m" -p 2181:2181 zookeeper
$ docker exec -it zookeeper /bin/bash
$ /apache-zookeeper-3.6.2-bin/bin/zkCli.sh

https://hub.docker.com/r/wurstmeister/kafka/

7. 安装kafka

提前安装并启动zookeeper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# KAFKA_BROKER_ID:kafka集群节点id(唯一)
# KAFKA_ZOOKEEPER_CONNECT:zookeeper连接地址
# KAFKA_ADVERTISED_LISTENERS:广播地址端口,即客户端访问的具体地址(会把这个地址端口注册给zookeeper,以备消费者获取)
# KAFKA_LISTENERS:外部代理地址和监听端口(限制访问ip以及服务端运行的端口)
# KAFKA_LOG_RETENTION_HOURS: 数据文件保留时间(非必选。缺省168小时,即7天)
# KAFKA_NUM_PARTITIONS:topic的分区数(非必选)
# KAFKA_DEFAULT_REPLICATION_FACTOR:分区的副本数(非必选)
$ docker run --name kafka \
-p 9092:9092 \
-v /opt/kafka:/kafka \
-v /etc/localtime:/etc/localtime:ro \
-e KAFKA_BROKER_ID=0 \
-e KAFKA_ZOOKEEPER_CONNECT=39.106.195.202:2181 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://39.106.195.202:9092 \
-e KAFKA_LISTENERS=PLAINTEXT://0.0.0.0:9092 \
-d wurstmeister/kafka

8. 安装ubuntu

1
2
3
4
5
6
7
8
#-i 表示 interactive 可交互的,变即可以从标准输入与容器交互。-t 表示给容器分配一个虚拟终端。-d 这个参数表示的是在后台运行,即 –deamon。-v 挂载容器目录到宿主机指定目录
docker run --name ubuntu-test -itd -v /root/docker-temp:/root/docker-temp ubuntu
#进入容器内部
docker exec -it ubuntu-test /bin/bash
#更新软件源,这个步骤优先级最高,因为不更新很多组件安装不了
apt-get update
#安装常用工具
apt install telnet iputils-ping net-tools curl wget vim

9. 安装centos

1
2
3
4
5
6
7
8
9
10
11
12
docker pull centos:centos7
#-i 表示 interactive 可交互的,变即可以从标准输入与容器交互。
#-t 表示给容器分配一个虚拟终端。
#-d 这个参数表示的是在后台运行,即 –deamon。
#-v 挂载容器目录到宿主机指定目录
docker run -itd --name centos7-test -v /root/docker-temp:/root/docker-temp centos:centos7
#进入容器内部
docker exec -it centos7-test bash
#更新软件源
yum update
#安装常用工具
yum install telnet net-tools curl wget vim -y

centos容器启用sshd服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#创建具有更高root权限的centos7容器,比如使用sshd服务等
#--privileged=true 使用该参数,container内的root拥有真正的root权限,否则,container内的root只是外部的一个普通用户权限,执行高权限命令是报错:Failed to get D-Bus connection: Operation not permitted
docker run -itd --name centos7-test --privileged=true centos:centos7 /usr/sbin/init
docker exec -it centos7-test bash
yum install telnet net-tools curl wget vim -y

#安装openssh
yum install openssh openssh-clients openssh-server -y
#编辑sshd_config配置文件,找到PermitRootLogin并将其值修改为yes,表示允许root管理员登录系统。将PasswordAuthentication的值改为yes表示使用帐号密码方式登录系统。
vim /etc/ssh/sshd_config
#给容器的root用户设置密码
passwd root

#启动sshd
systemctl start sshd
#其他sshd命令
#systemctl restart|stop|start|status sshd
#开机启动
systemctl enable sshd.service

#通过ifconfig查看docker容器的ip,然后在宿主机上进行ssh登录。如果要在宿主机外的其他机器登录,则需要docker run时映射端口到容器的22端口,如-p 2222:22
ssh root@172.18.0.3

十三、问题排错

(1)Error response from daemon: oci runtime error: container_linux.go:247: start....,原因是系统和docker版本不兼容。解决:执行yum update

(2)rying to pull repository docker.io/library/mysql ... Get https://registry-1.。解决:执行以下命令:

1
2
3
4
$ yum install bind-utils     #安装dig工具
$ dig @114.114.114.114 registry-1.docker.io
$ vi /etc/hosts
52.54.155.177 registry-1.docker.io

(3)[root@izuf6dskn3b7v2sly08bqtz ~]# docker pull mysql Using default tag: latest Trying to pull repository docker.io/library/mysql ... Get https://registry-1.docker.io/v2/: x509: certificate is valid for *.theranest.com, theranest.com, not registry-1.docker.io

解决:镜像加速解决

(3)执行docker-compose up -d后出现错误:ERROR: Failed to Setup IP tables: Unable to enable SKIP DNAT rule: (iptables failed: iptables --wait -t nat -I DOCKER -i br-950144d461c2 -j RETURN: iptables: No chain/target/match by that name. (exit status 1))

重启docker试试

1
2
$ systemctl stop docker
$ systemctl start docker

十四、参考

📚《Docker — 从入门到实践》



----------- 本文结束 -----------




如果你觉得我的文章对你有帮助,你可以打赏我哦~
0%