Docker 容器-高级篇
Docker 复杂应用安装
MySQL 主从复制
主从搭建步骤:
1 | docker run -p 3307:3306 --name mysql-master \ |
- 进入/mydata/mysql-master/conf 目录下新建 my.cnf
1 | [root@88231 conf]# vim my.cnf |
- 修改完配置后重启 master 实例
1 | [root@88231 conf] |
- 进入 mysql-master 容器
1 | [root@88231 conf]# docker exec -it mysql-master /bin/bash |
- master 容器实例内创建数据同步用户
1 | mysql> CREATE USER 'slave'@'%' IDENTIFIED BY '123456'; |
1 | docker run -p 3308:3306 --name mysql-slave \ |
- 进入/mydata/mysql-slave/conf 目录下新建 my.cnf
1 | [root@88231 conf]# vim my.cnf |
- 修改完配置后重启 slave 实例
1 | [root@88231 conf]# docker restart mysql-slave |
- 在主数据库中查看主从同步状态
1 | mysql> show master status; |
- 进入 mysql-slave 容器
1 | [root@88231 conf]# docker exec -it mysql-slave /bin/bash |
- 在从数据库中配置主从复制
1 | change master to master_host='宿主机ip', master_user='slave', master_password='123456', master_port=3307, master_log_file='mall-mysql-bin.000001', master_log_pos=617, master_connect_retry=30; |
- 在从数据库中查看主从同步状态
1 | mysql> show slave status \G; |
- 在从数据库中开启主从同步
1 | mysql> start slave; |
- 查看从数据库状态发现已经同步
1 | mysql> show slave status \G; |
- 主从复制测试
1 | - 主机新建库-使用库-新建表-插入数据,ok |
Redis 集群
搭建 Redis 集群
3 主 3 从 redis 集群扩缩容配置案例架构说明
https://www.processon.com/view/link/629e20255653bb03f2cc0a14
- 新建 6 个 docker 容器 redis 实例
1 | docker run -d --name redis-node-1 --net host --privileged=true -v /data/redis/share/redis-node-1:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6381 |
==如果运行成功,效果如下:==
命令分步解释:
docker run:创建并运行 docker 容器实例
–name redis-node-6:容器名字
–net host:使用宿主机的 IP 和端口,默认
–privileged=true:获取宿主机 root 用户权限
-v /data/redis/share/redis-node-6:/data:容器卷,宿主机地址:docker 内部地址
redis:6.0.8:redis 镜像和版本号
–cluster-enabled yes:开启 redis 集群
–appendonly yes:开启持久化
–port 6386:redis 端口号
- 进入容器 redis-node-1 并为 6 台机器构建集群关系
1 | # 进入容器 |
==一切 OK 的话,3 主 3 从搞定==
- 链接进入 6381 作为切入点,查看集群状态
1 | root@88231:/data# redis-cli -p 6381 |
主从容错切换迁移
数据读写存储
- 启动 6 个 redis 构成的集群并通过 exec 进入
- 对 6381 新增两个 key
- 防止路由失效加参数 -c 并新增两个 key
- 查看集群信息
1 | redis-cli --cluster check 192.168.88.231:6381 |
容错切换迁移
- 主 6381 和从机切换,先停止主机 6381
- 6381 主机停了,对应的真实从机上位
- 6381 作为 1 号主机分配的从机以实际情况为准,具体是几号机器就是几号
- 再次查看集群信息
==6381 宕机了,6385 上位成了新的 master==
备注:本次操作 6381 为主节点,对应的从节点是 6385,对应关系是随机的,每次操作以实际情况为准
- 启动 6381 节点
1 | docker start redis-node-1 |
- 再停 6385 节点
1 | docker stop redis-node-5 |
- 再启 6385 节点
1 | docker start redis-node-5 |
==发现主从节点又恢复之前的状态了==
- 查看集群状态
1 | redis-cli --cluster check 自己IP:6381 |
主从扩容案例
- 新建 6387、6388 两个节点+新建后启动+查看是否是 8 节点
1 | docker run -d --name redis-node-7 --net host --privileged=true -v /data/redis/share/redis-node-7:/data redis:6.0.8 --cluster-enabled yes --appendonly yes --port 6387 |
- 进入 6387 容器实例内部
1 | docker exec -it redis-node-7 /bin/bash |
- 将新增的 6387 节点(空槽号)作为 master 节点加入集群
1 | redis-cli --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381 |
- 检查集群情况第 1 次
1 | redis-cli --cluster check 真实ip地址:6381 |
- 重新分派槽号
1 | 重新分派槽号 |
- 检查集群情况第 2 次
1 | redis-cli --cluster check 真实ip地址:6381 |
==槽号分派说明==
为什么 6387 是 3 个新的区间,以前的还是连续?
重新分配成本太高,所以前 3 家各自匀出来一部分,从 6381/6382/6383 三个旧节点分别匀出 1364 个坑位给新节点 6387
- 为主节点 6387 分配从节点 6388
1 | 命令:redis-cli --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID |
- 检查集群第 3 次
1 | redis-cli --cluster check 192.168.88.231:6382 |
主从缩容案例
目的:6387 和 6388 下线
- 检查集群情况 - 获得 6388 的节点 ID
1 | redis-cli --cluster check 192.168.88.231:6382 |
- 将 6388 删除 从集群中将 4 号从节点 6388 删除
1 | 命令:redis-cli --cluster del-node ip:从机端口 从机6388节点ID |
1 | redis-cli --cluster check 192.168.88.231:6382 |
==检查一下发现,6388 被删除了,只剩下七台机器了。==
- 将 6387 的槽号清空,重新分配,本例将清出来的槽号都给 6381
1 | redis-cli --cluster reshard 192.168.88.231:6381 |
将 6387 节点的槽号都分配给 6381
- 检查集群情况
1 | redis-cli --cluster check 192.168.88.231 6382 |
- 删除 6387 节点
1 | 命令:redis-cli --cluster del-node ip:端口 6387节点ID |
- 再次检查集群情况
1 | redis-cli --cluster check 192.168.88.231 6382 |
DockerFile
Dockerfile 介绍
Dockerfile
是用来构建 Docker 镜像的文本文件,是由一条条构建镜像所需的指令和参数构成的脚本。
构建步骤:
- 编写一个
Dockerfile
文件 docker bulid
构建为一个镜像docker run
运行镜像docker push
发布镜像(DockerHub. 阿里云镜像仓库)
Dockerfile 构建过程
Dockerfile 基础知识
- 每个保留关键字(指令)都==必须是大写字母==且后面要跟随至少一个参数
- 指令按照从上到下,顺序执行
#
表示注释- 每条指令都会创建一个新的镜像层并对镜像进行提交
Docker 执行 Dockerfile 的大致流程
- docker 从基础镜像运行一个容器
- 执行一条指令并对容器做出修改
- docker 再基于刚提交的镜像运行一个新容器
- 执行 Dockerfile 中的下一条指令知道所有指令都执行完成
总结
从应用软件的角度看,Dockerfile
、Docker镜像
与Docker容器
分别代表软件的三个不同阶段:
Dockerfile
是软件的原材料Docker镜像
是软件的交付品Docker容器
则可以认为是软件镜像的运行态,也即依照镜像运行的容器实例
==Dockerfile 面向开发,Docker 镜像成为交付标准,Docker 容器则涉及部署与运维,三者缺一不可,合力充当 Docker 体系的基石==
Dockerfile,需要定义一个 Dockerfile,Dockerfile 定义了进程需要的一切东西。Dockerfile 涉及的内容包括执行代码或者是文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道,这时需要考虑如何设计 namespace 的权限控制)等等;
Docker 镜像,在用 Dockerfile 定义一个文件之后,docker build 时会产生一个 Docker 镜像,当运行 Docker 镜像时会真正开始提供服务;
Docker 容器,容器是直接提供服务的。
DockerFile 的保留字指令
FROM
:基础镜像,当前新镜像是基于哪个镜像的,指定一个已经存在的镜像作为模板,第一条必须是 FROMMAINTAINER
:镜像维护者的姓名和邮箱地址RUN
:容器构建时需要运行的命令,包含两种格式:- shell 格式:
- exec 格式:
RUN
是在docker build
时运行
EXPOSE
:当前容器对外暴露出的端口WORKDIR
:指定在创建容器后,终端默认登陆的进来工作目录,一个落脚点USER
:指定该镜像以什么样的用户去执行,如果都不指定,默认是 rootENV
:用来在构建镜像过程中设置环境变量- ```
ENV MY_PATH /usr/mytest
这个环境变量可以在后续的任何RUN指令中使用,这就如同在命令前面指定了环境变量前缀一样;
也可以在其它指令中直接使用这些环境变量,比如:WORKDIR $MY_PATH1
2
3
4
5
6
7
8
9
10
11
12
13
- `ADD`:将宿主机目录下的文件拷贝进镜像且会自动处理 URL 和解压 tar 压缩包
- `COPY`:类似 ADD,拷贝文件和目录到镜像中
- ```dockerfile
# 将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置
COPY src dest
COPY ["src", "dest"]
# <src源路径>:源文件或者源目录
# <dest目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。
- ```
VOLUME
:容器数据卷,用于数据保存和持久化工作CMD
:指定容器启动后的要干的事情- 注意:Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换
- 它和前面
RUN
命令的区别:CMD
是在docker run
时运行RUN
是在docker build
时运行
ENTRYPOINT
:也是用来指定一个容器启动时要运行的命令类似于 CMD 指令,但是 ENTRYPOINT 不会被 docker run 后面的命令覆盖, 而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序
ENTRYPOINT ["<executeable>","<param1>","<param2>"...]
ENTRYPOINT 可以和 CMD 一起用,一般是变参才会使用 CMD,这里的 CMD 等于是在给 ENTRYPOINT 传参。当指定了 ENTRYPOINT 后,CMD 的含义就发生了变化,不再是直接运行其命令而是将 CMD 的内容作为参数传递给 ENTRYPOINT 指令,他两个组合会变成
<ENTRYPOINT> "<CMD>"
优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数
注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效
小总结
实战案例
自定义镜像 ==> mycentosjava8
要求:
- Centos7 镜像具备 vim+ifconfig+jdk8
- 准备 jdk8 的安装包(
jdk-8u251-linux-x64.tar.gz
)
- 准备编写 Dockerfile 文件
在 /home
目录下建一个 myfile
文件夹,并将 jdk8
的安装包放进去:
Dockerfile
文件内容如下:
1 | FROM centos:centos7 |
- 执行构建命令
1 | docker build -t 新镜像名:TAG . |
注意:命令要在
Dockerfile
的同级目录下执行,不要忘了命令结尾的.
- 运行容器
1 | docker run -it centosjava8:1.0 /bin/bash |
虚悬镜像
虚悬镜像就是仓库名、标签都是 <none>
的镜像,也称为 dangling image
用 Dockerfile 生成一个
1 | # 编写 Dockerfile 文件 |
查看虚悬镜像
1 | docker images -f dangling=true |
删除所有虚悬镜像
虚悬镜像已经失去存在价值,可以删除
1 | docker image prune |
发布自己的镜像
DockerHub
地址 https://hub.docker.com/ 注册自己的账号!
确保这个账号可以登录
在服务器上提交自己的镜像
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18[root@ouwen ~]# docker login --help
Usage: docker login [OPTIONS] [SERVER]
Log in to a Docker registry.
If no server is specified, the default is defined by the daemon.
Options:
-p, --password string Password
--password-stdin Take the password from stdin
-u, --username string Username
[root@ouwen ~]# docker login -u ouwen666
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 login
登录之后就可以提交镜像了1
2
3
41. 使用 docker tag 命令修改镜像版本
[root@ouwen ~]# docker tag 352abc3918b1 ouwen666/tomcat:1.0
2. 使用 docker push 命令提交镜像到 DockerHub
[root@ouwen ~]# docker push ouwen666/tomcat:1.0发现:提交的时候也是按照镜像的层级来的!
阿里云镜像
登录阿里云
找到容器镜像服务
创建镜像仓库
浏览仓库信息
总结
Docker 网络
docker0
网卡
docker 服务启动后,会产生一个名为 docker0
的虚拟网桥,使用 ip addr
查看本机 ip
docker 网络常用命令
All 命令
1 | docker network --help |
查看网络
1 | docker network ls |
查看网络源数据
1 | docker network inspect xxx |
创建网络
1 | docker network create xxx |
删除网络
1 | docker network rm xxx |
docker 网络能干嘛?
- 容器间的互联和通信以及端口映射
- 容器 IP 变动时可以通过服务名直接进行网络通信,进而不受到影响
网络模式
- bridge 模式:使用 –network bridge 指定,默认使用 docker0
- host 模式:使用 –network host 指定
- none 模式:使用 –network none 指定
- container 模式:使用 –network container:NAME 或者容器 ID 指定
容器实例内默认网络 IP 生产规则
- 先启动两个 Ubuntu 容器实例
docker inspect 容器 ID or 容器名字
- 关闭 u2 实例,新建 u3,查看 IP 变化
结论:
docker 容器内部的 IP 是有可能会发生变化的
bridge
Docker 服务默认会创建一个 docker0 网桥(其上有一个 docker0 内部接口),该桥接网络的名称为 docker0,它在内核层连通了其他的物理或虚拟网卡,这就将所有容器和本地主机都放到同一个物理网络。Docker 默认指定了 docker0 接口 的 IP 地址和子网掩码,让主机和容器之间可以通过网桥相互通信。
1 | # 查看 bridge 网络的详细信息,并通过 grep 获取名称项 |
1 | ifconfig | grep docker |
说明:
Docker 使用 Linux 桥接,在宿主机虚拟一个 Docker 容器网桥(docker0),Docker 启动一个容器时会根据 Docker 网桥的网段分配给容器一个 IP 地址,称为 Container-IP,同时 Docker 网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个网桥,这样容器之间就能够通过容器的 Container-IP 直接通信
docker run 的时候,没有指定 network 的话默认使用的网桥模式就是 bridge,使用的就是 docker0。在宿主机 ifconfig,就可以看到 docker0 和自己 create 的 network(后面讲)eth0,eth1,eth2……代表网卡一,网卡二,网卡三……,lo 代表 127.0.0.1,即 localhost,inet addr 用来表示网卡的 IP 地址
网桥 docker0 创建一对对等虚拟设备接口一个叫 veth,另一个叫 eth0,成对匹配
- 整个宿主机的网桥模式都是 docker0,类似一个交换机有一堆接口,每个接口叫 veth,在本地主机和容器内分别创建一个虚拟接口,并让他们彼此联通(这样一对接口叫 veth pair)
- 每个容器实例内部也有一块网卡,每个接口叫 eth0
- docker0 上面的每个 veth 匹配某个容器实例内部的 eth0,两两配对,一一匹配
通过上述,将宿主机上的所有容器都连接到这个内部网络上,两个容器在同一个网络下,会从这个网关下各自拿到分配的 ip,此时两个容器的网络是互通的。
两两匹配验证
1 | docker run -d -p 8081:8080 --name tomcat81 billygoo/tomcat8-jdk8 |
host
直接使用宿主机的 IP 地址与外界进行通信,不再需要额外进行 NAT 转换。
说明:
容器将不会获得一个独立的 Network Namespace, 而是和宿主机共用一个 Network Namespace。容器将不会虚拟出自己的网卡而是使用宿主机的 IP 和端口。
验证
1 | docker run -d -p 8083:8080 --network host --name tomcat83 billygoo/tomcat8-jdk8 |
问题:
docker 容器启动时出现了警告
原因:
docker 启动时指定 –network=host 或 -net=host,如果还指定了 -p 映射端口,那这个时候就会有此警告,并且通过-p 设置的参数将不会起到任何作用,端口号会以主机端口号为主,重复时则递增。
解决:
解决的办法就是使用 docker 的其他网络模式,例如 –network=bridge,这样就可以解决问题,或者直接无视…
正确做法:
1 | docker run -d --network host --name tomcat83 billygoo/tomcat8-jdk8 |
这样就不会出现之前的警告了,查看容器实例内部:
没有设置-p 的端口映射了,如何访问启动的 tomcat83?
http://宿主机IP:8080/
在 CentOS 里面用默认的火狐浏览器访问容器内的 tomcat83 看到访问成功,因为此时容器的 IP 借用主机的,所以容器共享宿主机网络 IP,这样的好处是外部主机与容器可以直接通信。
none
在 none 模式下,并不为 Docker 容器进行任何网络配置。 也就是说,这个 Docker 容器没有网卡、IP、路由等信息,只有一个 lo,需要我们自己为 Docker 容器添加网卡、配置 IP 等。禁用网络功能,只有 lo 标识(就是 127.0.0.1 表示本地回环)
验证
1 | docker run -d -p 8084:8080 --network none --name tomcat84 billygoo/tomcat8-jdk8 |
进入容器内部查看
在容器外部查看
container
新建的容器和已经存在的一个容器共享一个网络 IP 配置而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的 IP,而是和一个指定的容器共享 IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。
验证
1 | docker run -d -p 8085:8080 --name tomcat85 billygoo/tomcat8-jdk8 |
运行结果:
相当于 tomcat86 和 tomcat85 公用同一个 ip 同一个端口,导致端口冲突!
换一个镜像进行验证
Alpine 操作系统是一个面向安全的轻型 Linux 发行版
Alpine Linux 是一款独立的、非商业的通用 Linux 发行版,专为追求安全性、简单性和资源效率的用户而设计。 可能很多人没听说过这个 Linux 发行版本,但是经常用 Docker 的朋友可能都用过,因为他小,简单,安全而著称,所以作为基础镜像是非常好的一个选择,可谓是麻雀虽小但五脏俱全,镜像非常小巧,不到 6M 的大小,所以特别适合容器打包。
1 | docker run -it --name alpine1 alpine /bin/sh |
假如此时关闭 alpine1,再看看 alpin2
发现 107: eth0@if108
已经消失
自定义网络
过时的 link
使用自定义网络的好处
before
1 | docker run -d -p 8081:8080 --name tomcat81 billygoo/tomcat8-jdk8 |
启动成功使用 docker exec 进入到容器内部
按照 IP 地址 ping 是 OK 的
但是无法按照服务名 ping
after
自定义桥接网络,自定义网络默认使用的就是桥接网络 – bridge
- 新建自定义网络
- 新建容器并加入上一步新建的自定义网络
1 | docker run -d -p 8081:8080 --network my_network --name tomcat81 billygoo/tomcat8-jdk8 |
- 互相 ping 测试
结论
自定义网络本身就维护好了主机名和 ip 的对应关系(ip 和域名都能通)
Docker 平台架构图解
整体说明
从其架构和运行流程来看,Docker 是一个 C/S 模式的架构,后端是一个松耦合架构,众多模块各司其职。
Docker 运行的基本流程为:
- 用户是使用 Docker Client 与 Docker Daemon 建立通信,并发送请求给后者。
- Docker Daemon 作为 Docker 架构中的主体部分,首先提供 Docker Server 的功能使其可以接受 Docker Client 的请求。
- Docker Engine 执行 Docker 内部的一系列工作,每一项工作都是以一个 Job 的形式的存在。
- Job 的运行过程中,当需要容器镜像时,则从 Docker Registry 中下载镜像,并通过镜像管理驱动 Graph driver 将下载镜像以 Graph 的形式存储。
- 当需要为 Docker 创建网络环境时,通过网络管理驱动 Network driver 创建并配置 Docker 容器网络环境。
- 当需要限制 Docker 容器运行资源或执行用户指令等操作时,则通过 Execdriver 来完成。
- Libcontainer 是一项独立的容器管理包,Network driver 以及 Exec driver 都是通过 Libcontainer 来实现具体对容器进行的操作。
整体架构
Docker Compose
什么是 Docker Compose
Docker Compose
是一个用于定义和运行多容器 Docker
应用程序的工具。使用 Compose
,您可以使用 YAML
文件来配置应用程序的服务。然后,使用单个命令,从配置创建并启动所有服务
使用 Docker Compose
基本上有以下三步:
- 使用 定义应用的环境,以便可以在任何位置重现它。
Dockerfile
- 定义构成应用的服务,以便它们可以在隔离的环境中一起运行。
docker-compose.yml
- 运行Docker Compose将启动并运行整个应用。您也可以使用 docker-compose 二进制文件运行。
docker compose up``docker-compose up
docker-compose.yml
示例:
1 | version: "3.9" # optional since v1.27.0 |
安装 Docker Compose
- 下载
1 | # 官网地址 |
- 给
docker-compose
文件授可执行权限
1 | sudo chmod +x /usr/local/bin/docker-compose |
- 测试安装是否成功
1 | docker-compose --version |
- 卸载
docker-compose
1 | rm /usr/local/bin/docker-compose |
Compose 核心概念
一文件
docker-compose.yml
两要素
- 服务(service)
一个个应用容器实例,比如订单微服务、库存微服务、mysql 容器、nginx 容器或者 redis 容器
- 工程(project)
由一组关联的应用容器组成的一个完整业务单元,在 docker-compose.yml 文件中定义
Compose 使用的三个步骤
- 编写 Dockerfile 定义各个微服务应用并构建出对应的镜像文件
- 使用 docker-compose.yml 定义一个完整业务单元,安排好整体应用中的各个容器服务
- 最后,执行 docker-compose up 命令 来启动并运行整个应用程序,完成一键部署上线
Compose 常用命令
查看帮助信息
1 | docker-compose -h|help |
命令选项
- -f, –file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定;
- -p, –project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名;
- –x-networking 使用 Docker 的可拔插网络后端特性;
- –x-network-driver DRIVER 指定网络后端的驱动,默认为 bridge;
- –verbose 输出更多调试信息;
- -v, –version 打印版本并退出;
- -H, –host HOST,远程操作 docker,被操作的 docker 需要开放 2375 端口
常用命令
- up:启动所有 docker-compose 服务
1 | docker-compose up |
- down:停止并删除容器、网络、卷
1 | docker-compose down |
- exec:进入容器实例内部
1 | docker-compose exec docker-compose.yml文件中写的服务id /bin/bash |
- ps:展示当前 docker-compose 编排过的运行的所有容器
1 | docker-compose ps |
- top:展示当前 docker-compose 编排过的容器进程
1 | docker-compose top |
- logs:查看容器输出日志
1 | docker-compose logs yml里面的服务id |
- build:构建或者重新构建服务
1 | docker-compose build |
- start:启动服务
1 | docker-compose start yml里面的服务id |
- stop:停止服务
1 | docker-compose stop yml里面的服务id |
- restart:重启服务
1 | docker-compose restart yml里面的服务id |
- config:检查配置
1 | docker-compose config |
- scale:设置指定服务运行容器的个数,以
service=num
形式指定
1 | docker-compose scale yml里面的服务id=实例个数 |
docker-compose.yml 文件规则
官网地址:https://docs.docker.com/compose/compose-file/compose-file-v3/
- version:指定 docker-compose.yml 文件的写法格式
- services:服务,多个容器集合
- build:配置构建时,Compose 会利用它自动构建镜像,该值可以是一个路径,也可以是一个对象,用于指定 Dockerfile 参数
1 | build: ./dir |
- command:覆盖容器启动后默认执行的命令
1 | command: bundle exec thin -p 3000 |
- dns:配置 dns 服务器,可以是一个值或列表
1 | dns: 8.8.8.8 |
- dns_search:配置 DNS 搜索域,可以是一个值或列表
1 | dns_search: example.com |
- environment:环境变量配置,可以用数组或字典两种方式
1 | environment: |
- env_file:从文件中获取环境变量,可以指定一个文件路径或路径列表,其优先级低于 environment 指定的环境变量
1 | env_file: .env |
- expose:暴露端口,只将端口暴露给连接的服务,而不暴露给主机
1 | expose: |
- image:指定服务所使用的镜像
1 | image: java |
- network_mode:设置网络模式
1 | network_mode: "bridge" |
- ports:对外暴露的端口定义,和 expose 对应
1 | ports: # 暴露端口信息 - "宿主机端口:容器暴露端口" |
- links:将指定容器连接到当前连接,可以设置别名,避免 ip 方式导致的容器重启动态改变的无法连接情况
1 | links: # 指定服务名称:别名 |
- volumes:卷挂载路径
1 | volumes: |
Docker 轻量级可视化工具 Portainer
Portainer 是一款轻量级的应用,它提供了图形化界面,用于方便地管理 Docker 环境,包括单机环境和集群环境。
docker 命令安装
1 | # 官方版本 |
第一次登录需要创建 admin 用户,访问地址:xxx.xxx.xxx.xxx:9010
选择 local 选项卡后本地 docker 详细信息展示
管理本地环境
Docker Swarm
什么是 Docker Swarm
Swarm 是 Docker 官方提供的一款集群管理工具,其主要作用是把若干台 Docker 主机抽象为一个整体,并且通过一个入口统一管理这些 Docker 主机上的各种 Docker 资源。
Docker Swarm 和 Docker Compose 一样,都是 Docker 官方容器编排项目,但不同的是,Docker Compose 是一个在单个服务器或主机上创建多个容器的工具,而 Docker Swarm 则可以在多个服务器或主机上创建容器集群服务。
Docker Swarm 架构图
上图可以看出,Swarm 是典型的 master-slave 结构,通过发现服务来选举 manager。manager 是中心管理节点,各个 node 上运行 agent 接受 manager 的统一管理,集群会自动通过 Raft 协议分布式选举出 manager 节点,无需额外的发现服务支持,避免了单点的瓶颈问题,同时也内置了 DNS 的负载均衡和对外部负载均衡机制的集成支持。
Swarm 关键概念
- Swarm(群)
嵌入在 Docker 引擎中的集群管理和编排功能是使用swarmkit 构建的。Swarmkit
是一个单独的项目,它实现了 Docker 的编排层并直接在 Docker 中使用。
一个 swarm 由多个 Docker 主机组成,它们以swarm 模式运行并充当管理器(管理成员资格和委托)和工作人员(运行 swarm 服务)。给定的 Docker 主机可以是管理员、工作人员或同时执行这两种角色。创建服务时,您需要定义其最佳状态(副本数量、可用的网络和存储资源、服务向外界公开的端口等等)。Docker 致力于维护所需的状态。例如,如果一个工作节点变得不可用,Docker 会将该节点的任务安排在其他节点上。任务是一个 正在运行的容器,它是一个集群服务的一部分,由一个集群管理器管理,而不是一个独立的容器。
swarm 服务相对于独立容器的主要优势之一是您可以修改服务的配置,包括它所连接的网络和卷,而无需手动重新启动服务。Docker 将更新配置,停止具有过期配置的服务任务,并创建与所需配置匹配的新任务。
当 Docker 在 swarm 模式下运行时,您仍然可以在任何参与 swarm 的 Docker 主机以及 swarm 服务上运行独立容器。独立容器和 swarm 服务之间的一个关键区别在于,只有 swarm manager
可以管理 swarm,而独立容器可以在任何守护进程上启动。Docker 守护进程可以作为管理者、工作者或两者兼而有之。
就像您可以使用Docker Compose定义和运行容器一样,您可以定义和运行Swarm 服务堆栈。
- Node(节点)
节点是参与 swarm 的 Docker 引擎的一个实例。您也可以将其视为 Docker 节点。您可以在单个物理计算机或云服务器上运行一个或多个节点,但生产群部署通常包括分布在多个物理和云计算机上的 Docker 节点。
要将应用程序部署到 swarm,您需要向 管理器节点提交服务定义。管理节点将称为 任务的工作单元分派给工作节点。
管理器节点还执行维护集群所需状态所需的编排和集群管理功能。管理器节点选举一个领导者来执行编排任务。
工作节点接收并执行从管理节点分派的任务。默认情况下,管理器节点也将服务作为工作节点运行,但您可以将它们配置为专门运行管理器任务并成为仅管理器节点。代理在每个工作节点上运行并报告分配给它的任务。worker
节点将其分配的任务的当前状态通知给 manager
节点,以便 manager
可以保持每个 worker
的期望状态。
- Service(服务)
服务是要在管理节点或工作节点上执行的任务的定义。它是 swarm 系统的中心结构,也是用户与 swarm 交互的主要根源。
创建服务时,您需要指定要使用的容器映像以及在运行的容器中执行的命令。
在复制服务模型中,群管理器根据您在所需状态中设置的规模在节点之间分配特定数量的副本任务。
对于全局服务,swarm 在集群中的每个可用节点上为服务运行一个任务。
- Task(任务)
一个任务携带一个 Docker 容器和在容器内运行的命令。它是 swarm 的原子调度单元。Manager 节点根据服务规模中设置的副本数将任务分配给工作节点。一旦任务被分配给一个节点,它就不能移动到另一个节点。它只能在分配的节点上运行或失败。
- Load balancing(负载均衡)
swarm 管理器使用入口负载平衡将您希望在外部提供给 swarm 的服务公开。swarm manager 可以自动为服务分配一个PublishedPort,或者您可以为该服务配置一个 PublishedPort。您可以指定任何未使用的端口。如果不指定端口,swarm manager 会为服务分配一个 30000-32767
范围内的端口。
外部组件(例如云负载均衡器)可以访问集群中任何节点的 PublishedPort 上的服务,无论该节点当前是否正在运行该服务的任务。swarm 中的所有节点将入口连接路由到正在运行的任务实例。
Swarm 模式有一个内部 DNS
组件,它自动为 swarm 中的每个服务分配一个 DNS 条目。swarm manager 使用内部负载平衡根据服务的 DNS 名称在集群内的服务之间分配请求。
Swarm 工作原理
节点如何工作
Swarm 集群由管理节点(Manager)和工作节点(Work)构成。
管理节点
Manager 节点处理集群管理任务:
- 维护集群状态
- 调度服务
- 服务群模式HTTP API 端点
使用Raft实现,管理器维护整个 swarm 和在其上运行的所有服务的一致内部状态。出于测试目的,可以使用单个管理器运行 swarm。如果单管理器集群中的管理器发生故障,您的服务将继续运行,但您需要创建一个新集群才能恢复。
为了利用 swarm 模式的容错特性,Docker 建议您根据组织的高可用性要求实现奇数个节点。当您有多个管理器时,您可以从管理器节点的故障中恢复而无需停机。
- 一个三管理器群最多可以容忍一名管理器的损失。
- 一个五管理器群最多可以同时丢失两个管理器节点。
- 一个
N
管理器集群最多可以容忍丢失(N-1)/2
管理器。 - Docker 建议一个 swarm 最多使用七个管理器节点。
重要提示:添加更多管理器并不意味着增加可扩展性或提高性能。一般来说,情况正好相反。
工作节点
工作节点也是 Docker 引擎的实例,其唯一目的是执行容器。Worker 节点不参与 Raft 分布式状态,不做调度决策,也不服务于 swarm 模式的 HTTP API。
您可以创建一个由一个管理器节点组成的集群,但如果没有至少一个管理器节点,您就不能拥有一个工作程序节点。默认情况下,所有 manager 也是 worker。在单个管理节点集群中,您可以运行类似 docker service create
的命令,并且调度程序将所有任务放在本地引擎上。
要防止调度程序将任务放置在多节点集群中的管理器节点上,请将管理器节点的可用性设置为 Drain
。调度器优雅地停止 Drain
模式节点上的任务,并在一个 Active
节点上调度任务。调度程序不会将新任务分配给 Drain
可用的节点。
请参阅docker node update 命令行参考以了解如何更改节点可用性。
服务如何运作
要在 Docker 引擎处于 swarm 模式时部署应用程序映像,您需要创建一个服务。通常,服务是某个更大应用程序上下文中微服务的映像。服务的示例可能包括 HTTP 服务器、数据库或您希望在分布式环境中运行的任何其他类型的可执行程序。
创建服务时,您需要指定要使用的容器映像以及在运行的容器中执行的命令。您还可以定义服务的选项,包括:
- swarm 使服务在 swarm 外部可用的端口
- 服务连接到集群中的其他服务的覆盖网络
- CPU 和内存限制和预留
- 滚动更新策略
- 在 swarm 中运行的图像的副本数
服务、任务和容器
当您将服务部署到 swarm 时,swarm manager 接受您的服务定义作为服务的所需状态。然后,它将集群中的节点上的服务安排为一个或多个副本任务。这些任务在 swarm 中的节点上彼此独立运行。
例如,假设您想在 nginx 的三个实例之间进行负载平衡。下图显示了具有三个副本的 nginx 服务。nginx 的三个实例中的每一个都是 swarm 中的一个任务。
容器是一个独立的进程。在 swarm 模式模型中,每个任务只调用一个容器。任务类似于调度程序放置容器的“槽”。一旦容器处于活动状态,调度程序就会识别出任务处于运行状态。如果容器未通过健康检查或终止,则任务终止。
任务和调度
任务是 swarm 中调度的原子单元。当您通过创建或更新服务来声明所需的服务状态时,编排器通过调度任务来实现所需的状态。例如,您定义了一个服务,该服务指示协调器始终保持三个 HTTP 侦听器实例运行。编排器通过创建三个任务来响应。每个任务都是调度程序通过生成容器来填充的槽。容器是任务的实例化。如果 HTTP 侦听器任务随后未能通过其健康检查或崩溃,编排器将创建一个新的副本任务来生成一个新容器。
任务是一种单向机制。它通过一系列状态单调地进行:分配、准备、运行等。如果任务失败,编排器将删除任务及其容器,然后根据服务指定的所需状态创建一个新任务来替换它。
Docker swarm 模式的底层逻辑是一个通用的调度器和编排器。服务和任务抽象本身并不知道它们实现的容器。假设您可以实现其他类型的任务,例如虚拟机任务或非容器化流程任务。调度器和编排器不知道任务的类型。但是,当前版本的 Docker 只支持容器任务。
下图显示了 swarm 模式如何接受服务创建请求并将任务调度到工作节点。
待办服务(pending)
一个服务可以这样配置,使得当前在 swarm 中的任何节点都不能运行它的任务。在这种情况下,服务保持状态pending
。以下是服务可能保持状态的几个示例pending
。
注意:如果您的唯一目的是阻止部署服务,请将服务缩放到 0,而不是尝试将其配置为保留在
pending
.
- 如果所有节点都已暂停或耗尽,并且您创建了一项服务,则该服务将处于挂起状态,直到节点可用为止。实际上,第一个可用的节点会获得所有任务,因此这在生产环境中不是一件好事。
- 您可以为服务保留特定数量的内存。如果 swarm 中没有节点具有所需的内存量,则服务将保持挂起状态,直到可以运行其任务的节点可用。如果您指定一个非常大的值,例如 500 GB,则该任务将永远保持挂起状态,除非您确实有一个可以满足它的节点。
- 您可以对服务施加放置约束,并且这些约束可能无法在给定时间得到遵守。
这种行为说明您的任务的要求和配置与当前的 swarm 状态并没有紧密联系。作为 swarm 的管理员,您声明了您的 swarm 所需的状态,并且管理器与 swarm 中的节点一起创建该状态。您不需要对 swarm 上的任务进行微观管理。
部署模式
有两种类型的服务部署,复制的(replicated
)和全局的(global
)。
对于复制服务,您指定要运行的相同任务的数量。例如,您决定部署具有三个副本的 HTTP 服务,每个副本提供相同的内容。
全局服务是在每个节点上运行一个任务的服务。没有预先指定的任务数量。每次将节点添加到 swarm 时,编排器都会创建一个任务,调度器会将任务分配给新节点。全局服务的良好候选者是监控代理、防病毒扫描程序或您希望在集群中的每个节点上运行的其他类型的容器。
下图显示了黄色的三服务副本和灰色的全局服务。
Swarm 任务状态
Docker 允许创建可以启动任务的服务。服务是对所需状态的描述,而任务完成工作。工作按以下顺序安排在 swarm 节点上:
- 通过使用创建服务
docker service create
。 - 请求转到 Docker 管理器节点。
- Docker 管理器节点安排服务在特定节点上运行。
- 每个服务可以启动多个任务。
- 每个任务都有一个生命周期,其状态包括
NEW
、PENDING
和COMPLETE
。
任务是运行一次即可完成的执行单元。当一个任务停止时,它不会再次执行,但一个新的任务可能会取代它。
任务通过多个状态前进,直到它们完成或失败。任务在 NEW
状态中初始化。任务通过多个状态向前推进,并且其状态不会后退。例如,任务永远不会从 COMPLETE
到 RUNNING
。
任务按以下顺序通过状态:
任务状态 | 描述 |
---|---|
NEW |
任务已初始化。 |
PENDING |
分配了任务的资源。 |
ASSIGNED |
Docker 将任务分配给节点。 |
ACCEPTED |
该任务已被工作节点接受。如果工作节点拒绝任务,则状态更改为REJECTED 。 |
PREPARING |
Docker 正在准备任务。 |
STARTING |
Docker 正在启动任务。 |
RUNNING |
任务正在执行。 |
COMPLETE |
任务退出,没有错误代码。 |
FAILED |
任务以错误代码退出。 |
SHUTDOWN |
Docker 请求关闭任务。 |
REJECTED |
工作节点拒绝了该任务。 |
ORPHANED |
节点关闭时间过长。 |
REMOVE |
该任务不是终端,但相关服务已被删除或缩小。 |
查看任务状态
运行 docker service ps
以获取任务的状态。该 CURRENT STATE
字段显示任务的状态以及它在那里的时间。
常用命令
创建 Swarm
- 运行以下命令来创建一个新的 swarm:
1 | $ docker swarm init --advertise-addr <MANAGER-IP> |
- 例如:
1 | $ docker swarm init --advertise-addr 192.168.88.230 |
该
--advertise-addr
标志将管理节点配置为将其地址发布为192.168.88.230
。 swarm 中的其他节点必须能够访问该 IP 地址的 manager。输出包括将新节点加入 swarm 的命令。根据
--token
标志的值,节点将作为 manager 或 worker 加入。
- 运行
docker info
查看 swarm 的当前状态:
1 | $ docker info |
- 运行
docker node ls
命令查看节点信息:
1 | $ docker node ls |
将节点添加到 Swarm
当你创建了一个带有管理节点的 swarm 时,你就可以添加工作节点了。
- 运行创建 Swarm 中
docker swarm init
步骤生成的命令,以创建一个加入现有 swarm 的工作节点:
1 | $ docker swarm join --token SWMTKN-1-63d99h3uln0k8qfne7w39qt6f1tv4yddvevyzwh3uzhhfl73wf-3abelxhlyvhm54klavpbr06r5 192.168.88.230:2377 |
- 如果没有可用的命令,可以在管理节点上运行以下命令来生成工作节点的加入命令:
1 | $ docker swarm join-token worker |
- 生成管理节点的加入命令:
1 | $ docker swarm join-token manager |
删除节点
先在要删除的节点上运行:
1 | docker swarm leave |
在 manage
节点上运行:
1 | docker node rm NODE |
节点提升
用法
1 | docker node promote NODE [NODE...] |
说明
将节点提升为 manager
。该命令只能在 manage node
上执行。
笔记
这是一个集群管理命令,必须在 swarm manager 节点上执行。
节点降级
用法
1 | docker node demote NODE [NODE...] |
说明
降级现有 manager
,使其不再是 manager
。
笔记
这是一个集群管理命令,必须在 swarm manager 节点上执行。
部署服务
- 在管理节点上运行以下命令:
1 | $ docker service create --replicas 1 --name helloworld alpine ping docker.com |
- 该
docker service create
命令创建服务。 - 该
--name
标志命名服务helloworld
。 - 该
--replicas
标志指定 1 个正在运行的实例的所需状态。 - 参数
alpine ping docker.com
将服务定义为执行命令的 Alpine Linux 容器ping docker.com
。
- 运行
docker service ls
查看正在运行的服务列表:
1 | $ docker service ls |
检查服务
将服务部署到 swarm 后,可以使用 Docker CLI
查看有关在 swarm 中运行的服务的详细信息。
- 运行
docker service inspect --pretty <SERVICE-ID>
以易于阅读的格式显示有关服务的详细信息。
1 | $ docker service inspect --pretty helloworld |
提示:要以 json 格式返回服务详细信息,请运行不带
--pretty
标志的相同命令。
1 | $ docker service inspect helloworld |
- 运行
docker service ps <SERVICE-ID>
查看哪些节点正在运行该服务:
1 | $ docker service ps helloworld |
在这种情况下,服务的一个实例正在节点 helloworld
上运行 。8886
您可能会看到该服务在您的管理器节点上运行。默认情况下,swarm 中的管理节点可以像工作节点一样执行任务。
Swarm 还向您显示服务任务的 DESIRED STATE
和 CURRENT STATE
,以便您可以查看任务是否根据服务定义运行。
- 在运行
docker ps
任务的节点上运行以查看有关任务容器的详细信息。
提示:如果
helloworld
在管理节点以外的节点上运行,则必须 ssh 到该节点。
1 | $ docker ps |
扩展服务
将服务部署到 swarm 后,您就可以使用 Docker CLI
扩展服务中的容器数量了。在服务中运行的容器称为“任务”。
- 运行以下命令以更改在 swarm 中运行的服务的所需状态
1 | $ docker service scale <SERVICE-ID>=<NUMBER-OF-TASKS> |
例如:
1 | $ docker service scale helloworld=5 |
- 运行
docker service ps <SERVICE-ID>
查看更新的任务列表:
1 | $ docker service ps helloworld |
您可以看到 swarm 创建了 4 个新任务以扩展到总共 5 个正在运行的 Alpine Linux 实例。任务分布在 swarm 的三个节点之间。
删除服务
- 运行
docker service rm helloworld
以删除helloworld
服务。
1 | $ docker service rm helloworld |
应用滚动更新
- 将您的 Redis 标签部署到 swarm 并为 swarm 配置 10 秒的更新延迟。请注意,以下示例显示了较旧的 Redis 标记:
1 | $ docker service create \ |
您在服务部署时配置滚动更新策略。
该 --update-delay
标志配置更新服务任务或任务集之间的时间延迟。您可以将时间描述 T
为秒数 Ts
、分钟数 Tm
或小时数的组合 Th
。所以 10m30s
表示延迟 10 分 30 秒。
默认情况下,调度程序一次更新 1 个任务。您可以传递该 --update-parallelism
标志来配置调度程序同时更新的最大服务任务数。
默认情况下,当单个任务的更新返回状态为 时 RUNNING
,调度程序会安排另一个任务进行更新,直到所有任务都更新完毕。如果在更新期间的任何时间任务返回 FAILED
,调度程序会暂停更新。您可以使用或 的 --update-failure-action
标志来控制行为 。docker service create
docker service update
- 检查
redis
服务:
1 | $ docker service inspect --pretty redis |
- 现在您可以更新
redis
, swarm manager 根据UpdateConfig
策略将更新应用到节点:
1 | $ docker service update --image redis:3.0.7 redis |
默认情况下,调度程序按如下方式应用滚动更新:
- 停止第一个任务。
- 为已停止的任务安排更新。
- 启动更新任务的容器。
- 如果一个任务的更新返回
RUNNING
,等待指定的延迟时间然后开始下一个任务。 - 如果在更新期间的任何时间,任务返回
FAILED
,则暂停更新。
- 运行
docker service inspect --pretty redis
以查看处于所需状态的新图像:
1 | $ docker service inspect --pretty redis |
- 运行
docker service ps <SERVICE-ID>
以观看滚动更新:
1 | $ docker service ps redis |
排空一个节点
有时,例如计划的维护时间,您需要将节点设置为 DRAIN
可用。DRAIN
可用性阻止节点从集群管理器接收新任务。这也意味着管理器停止在节点上运行的任务,并在可用的节点上启动副本任务 ACTIVE
。
重要:将节点设置为
DRAIN
不会从该节点删除独立容器,例如使用 Docker 引擎 API 创建的docker run
容器docker-compose up
。一个节点的状态,包括DRAIN
,只影响节点调度 swarm 服务工作负载的能力。
- 运行
docker node update --availability drain <NODE-ID>
以排空已分配任务的节点:
1 | $ docker node update --availability drain worker1 |
- 检查节点其可用性:
1 | $ docker node inspect --pretty worker1 |
AVAILABILITY
属性值为 Drain
Docker Stack
deploy
指定与服务的部署和运行有关的配置。只在 swarm 模式下才会有用。
1 | version: "3.7" |
可以选参数:
endpoint_mode:访问集群服务的方式。
1 | endpoint_mode: vip |
labels:在服务上设置标签。可以用容器上的 labels(跟 deploy 同级的配置) 覆盖 deploy 下的 labels。
mode:指定服务提供的模式。
replicated:复制服务,复制指定服务到集群的机器上。
global:全局服务,服务将部署至集群的每个节点。
图解:下图中黄色的方块是 replicated 模式的运行情况,灰色方块是 global 模式的运行情况。
replicas:mode 为 replicated 时,需要使用此参数配置具体运行的节点数量。
resources:配置服务器资源使用的限制,例如上例子,配置 redis 集群运行需要的 cpu 的百分比 和 内存的占用。避免占用资源过高出现异常。
restart_policy:配置如何在退出容器时重新启动容器。
- condition:可选 none,on-failure 或者 any(默认值:any)。
- delay:设置多久之后重启(默认值:0)。
- max_attempts:尝试重新启动容器的次数,超出次数,则不再尝试(默认值:一直重试)。
- window:设置容器重启超时时间(默认值:0)。
rollback_config:配置在更新失败的情况下应如何回滚服务。
- parallelism:一次要回滚的容器数。如果设置为 0,则所有容器将同时回滚。
- delay:每个容器组回滚之间等待的时间(默认为 0s)。
- failure_action:如果回滚失败,该怎么办。其中一个 continue 或者 pause(默认 pause)。
- monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为 0s)。
- max_failure_ratio:在回滚期间可以容忍的故障率(默认为 0)。
- order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认 stop-first )。
update_config:配置应如何更新服务,对于配置滚动更新很有用。
- parallelism:一次更新的容器数。
- delay:在更新一组容器之间等待的时间。
- failure_action:如果更新失败,该怎么办。其中一个 continue,rollback 或者 pause (默认:pause)。
- monitor:每个容器更新后,持续观察是否失败了的时间 (ns|us|ms|s|m|h)(默认为 0s)。
- max_failure_ratio:在更新过程中可以容忍的故障率。
- order:回滚期间的操作顺序。其中一个 stop-first(串行回滚),或者 start-first(并行回滚)(默认 stop-first)。
注:仅支持 V3.4 及更高版本。