容器生态系统包含三个部分:
-
容器核心知识,包括架构、镜像、容器、网络和存储。
-
容器平台技术,包括容器编排引擎、容器管理平台和基于容器的 PaaS。
-
容器支持技术,包括多主机管理、跨主机网络方案、监控和日志管理。
之前介绍过docker一些相关知识,有关容器核心技术和容器支持技术,下面来介绍下容器平台技术,这也是这几年最火的云计算相关的知识,如果学习了会大有裨益。容器编排引擎、容器管理平台和基于容器的 PaaS 在大规模生产部署中占有相当重要的位置。其中容器编排引擎是核心,而最重要的编排引擎则是 Kubernetes,这当然也是这个教程的重中之重
本文只介绍docker自带的编排引擎docker swarm:
一、基本概念:
Docker Swarm 管理的是 Docker Host 集群。所以先来讨论一个重要的概念 - 集群化(Clustering)。
服务器集群由一组网络上相互连接的服务器组成,它们一起协同工作。一个集群和一堆服务器最显著的区别在于:
集群能够像 单个 系统那样工作,同时提供高可用、负载均衡和并行处理
1、swarm
swarm 运行 Docker Engine 的多个主机组成的集群。
没启动 swarm mode 时,Docker 执行的是容器命令;运行 swarm mode 后,Docker 增加了编排 service 的能力。
Docker 允许在同一个 Docker 主机上既运行 swarm service,又运行单独的容器。
2、node
swarm 中的每个 Docker Engine 都是一个 node,有两种类型的 node:manager 和 worker。
为了向 swarm 中部署应用,我们需要在 manager node 上执行部署命令,manager node 会将部署任务拆解并分配给一个或多个 worker node 完成部署。
manager node 负责执行编排和集群管理工作,保持并维护 swarm 处于期望的状态。swarm 中如果有多个 manager node,它们会自动协商并选举出一个 leader 执行编排任务。
woker node 接受并执行由 manager node 派发的任务。默认配置下 manager node 同时也是一个 worker node,不过可以将其配置成 manager-only node,让其专职负责编排和集群管理工作。
work node 会定期向 manager node 报告自己的状态和它正在执行的任务的状态,这样 manager 就可以维护整个集群的状态。
3、service
service 定义了 worker node 上要执行的任务。swarm 的主要编排任务就是保证 service 处于期望的状态下。
二、部署swarm 集群
下面将三个节点进行集群,基于下面这三个节点:
swarm-manager 是 manager node,swarm-worker1 和 swarm-worker2 是 worker node
首先在swarm manager节点上创建swarm:docker swarm init --advertise-addr 192.168.2.101
--advertise-addr
指定与其他 node 通信的地址。
docker swarm init
输出告诉我们:
① swarm 创建成功,swarm-manager 成为 manager node。
② 添加 worker node 需要执行的命令。
③ 添加 manager node 需要执行的命令。
复制前面的 docker swarm join
命令,在 swarm-worker1 和 swarm-worker2 上执行,将它们添加到 swarm 中。
docker node ls
可以看到两个 worker node 已经添加进来了。
如果当时没有记录下 docker swarm init
提示的添加 worker 的完整命令,可以通过 docker swarm join-token worker
查看。
创建好了 Swarm 集群, 现在部署一个运行 httpd 镜像的 service,执行如下命令:
docker service create --name web_server httpd
通过 docker service ls
可以查看当前 swarm 中的 service。
REPLICAS
显示当前副本信息,0/1
的意思是 web_server 这个 service 期望的容器副本数量为 1,目前已经启动的副本数量为 1。命令 docker service ps
可以查看 service 每个副本的状态。
三、弹性
部署了只有一个副本的 Service,不过对于 web 服务,我们通常会运行多个实例。这样可以负载均衡,同时也能提供高可用。
swarm 要实现这个目标非常简单,增加 service 的副本数就可以了。在 swarm-manager 上执行如下命令:
docker service scale web_server=5
也可以在创建服务的时候使用 --replicas=5:
docker service create --name web_server --replicas=5 httpd
副本数增加到 5,通过 docker service ls
和 docker service ps
查看副本的详细信息。
默认配置下 manager node 也是 worker node,所以 swarm-manager 上也运行了副本。如果不希望在 manager 上运行 service,可以执行如下命令:
docker node update --availability drain swarm-manager
通过 docker node ls
查看各节点现在的状态:
Drain
表示 swarm-manager 已经不负责运行 service,之前 swarm-manager 运行的那个副本会如何处理呢?用 docker service ps
查看一下:
四、网络通信
如果想将端口映射到host,使用--publish-add 8080:80
docker service update --publish-add 8080:80 web_server
或者创建的时候使用。
容器的网络与 --publish-add
之前已经大不一样了,现在有两块网卡,每块网卡连接不同的 Docker 网络
1、eth0 连接的是一个 overlay 类型的网络,名字为 ingress
,其作用是让运行在不同主机上的容器可以相互通信。
2、eth1 连接的是一个 bridge 类型的网络,名字为 docker_gwbridge
,其作用是让容器能够访问到外网。
ingress
网络是 swarm 创建时 Docker 为自动我们创建的,swarm 中的每个 node 都能使用 ingress
。
当我们访问任何节点的 8080 端口时,swarm 内部的 load balancer 会将请求转发给 web_server 其中的一个副本
微服务架构的应用由若干 service 组成。比如有运行 httpd 的 web 前端,有提供缓存的 memcached,有存放数据的 mysql,每一层都是 swarm 的一个 service,每个 service 运行了若干容器。在这样的架构中,service 之间是必然要通信的。怎样实现呢?那就得依靠服务发现了。
要使用服务发现,需要相互通信的 service 必须属于同一个 overlay 网络,所以我们先得创建一个新的 overlay 网络。
docker network create --driver overlay myapp_net
部署一个 web 服务,并将其挂载到新创建的 overlay 网络。
docker service create --name my_web --replicas=3 --network myapp_net httpd
部署一个 util 服务用于测试,挂载到同一个 overlay 网络。
docker service create --name util --network myapp_net busybox sleep 10000000
那么从一个容器就能ping通另外一个容器了。
五、更新和回滚:
下面我们将部署三副本的服务,镜像使用 httpd:2.2.31,然后将其更新到 httpd:2.2.32。
创建服务:
docker service create --name my_web --replicas=3 httpd:2.2.31
将 service 更新到 httpd:2.2.32:
docker service update --image httpd:2.2.32 my_web
默认配置下,Swarm 一次只更新一个副本,并且两个副本之间没有等待时间。我们可以通过 --update-parallelism
设置并行更新的副本数目,通过 --update-delay
指定滚动更新的间隔时间。
比如执行如下命令:
docker service update --replicas 6 --update-parallelism 2 --update-delay 1m30s my_web
service 增加到六个副本,每次更新两个副本,间隔时间一分半钟。
docker service inspect
查看 service 的当前配置。
Swarm 还有个方便的功能是回滚,如果更新后效果不理想,可以通过 --rollback
快速恢复到更新之前的状态。
docker service update --rollback
my_web
六、存储:
volume 不依赖 Docker 主机和容器,生命周期由 storage provider 管理,volume 的高可用和数据有效性也全权由 provider 负责,Docker 只管使用。
Rex-Ray 是开源的容器存储管理解决方案。支持主流的容器编排引擎 Docker Swarm、 Kubernetes 和 Mesos,为容器集群提供自动化的存储编排功能。
docker service create --name my_web \
--publish 8080:80 \
--mount "type=volume,volume-driver=rexray,source=web_data,target=/usr/local/apache2/htdocs" \
httpd
-
--mount
指定数据卷的volume-driver
为rexray
。 -
source
指定数据卷的名字为web_data
,如果不存在,则会新建。 -
target
指定将数据卷 mount 到每个副本容器的/usr/local/apache2/htdocs
,即存放静态页面的目录。
七、global
mode
Swarm 可以在 service 创建或运行过程中灵活地通过 --replicas
调整容器副本的数量,内部调度器则会根据当前集群的资源使用状况在不同 node 上启停容器,这就是 service 默认的 replicated
mode。在此模式下,node 上运行的副本数有多有少,一般情况下,资源更丰富的 node 运行的副本数更多,反之亦然。
除了 replicated
mode,service 还提供了一个 global
mode,其作用是强制在每个 node 上都运行一个且最多一个副本。
此模式特别适合需要运行 daemon 的集群环境。比如要收集所有容器的日志,就可以 global
mode 创建 service,在所有 node 上都运行 gliderlabs/logspout
容器,即使之后有新的 node 加入,swarm 也会自动在新 node 上启动一个 gliderlabs/logspout
副本。
docker service create \
--mode global \
--name logspout \
--mount type=bind,source=/var/run/docker.sock,destination=/var/run/docker.sock \
gliderlabs/logspout
作为用户我们有没有可能精细控制 Service 的运行位置呢?
答案是:能,使用 label。
逻辑分两步:
1、为每个 node 定义 label。
2、设置 service 运行在指定 label 的 node 上。
label 可以灵活描述 node 的属性,其形式是 key=value,用户可以任意指定,例如将 swarm-worker1
作为测试环境,为其添加 label env=test
:
docker node update --label-add env=test swarm-worker1
对应的,将 swarm-worker2
作为生产环境,添加 label env=prod
:
docker node update --label-add env=prod swarm-worker2
现在部署 service 到测试环境:
docker service create \
--constraint node.labels.env==test \
--replicas 3 \
--name my_web \
--publish 8080:80 \
httpd
--constraint node.labels.env==test
限制将 service 部署到 label=test 的 node,即 swarm-worker1
。从部署结果看,三个副本全部都运行在 swarm-worker1
上。
更新 service,将其迁移到生产环境:
docker service update --constraint-rm node.labels.env==test my_web
docker service update --constraint-add node.labels.env==prod my_web
删除并添加新的 constraint,设置 node.labels.env==prod
,最终所有副本都迁移到了 swarm-worker2
。
八、Health check配置:
下面我们通过例子来演示 Health Check 在 swarm 中的应用。
docker service create --name my_db \
--health-cmd "curl --fail http://localhost:8091/pools || exit 1" \
couchbase
--health-cmd
Health Check 的命令,还有几个相关的参数:
1、--timeout
命令超时的时间,默认 30s。
2、--interval
命令执行的间隔时间,默认 30s。
3、--retries
命令失败重试的次数,默认为 3,如果 3 次都失败了则会将容器标记为 unhealthy
。swarm 会销毁并重建 unhealthy
的副本。
九、secret
我们经常要向容器传递敏感信息,最常见的莫过于密码了。比如:
docker run -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql
在启动 MySQL 容器时我们通过环境变量 MYSQL_ROOT_PASSWORD
设置了 MySQL 的管理员密码。不过密码是以明文的形式写在 docker run
命令中,有潜在的安全隐患。
为了解决这个问题,docker swarm 提供了 secret 机制,允许将敏感信息加密后保存到 secret 中,用户可以指定哪些容器可以使用此 secret。
如果使用 secret 启动 MySQL 容器,方法是:
1、在 swarm manager 中创建 secret my_secret_data
,将密码保存其中。
echo "my-secret-pw" | docker secret create my_secret_data -
2、启动 MySQL service,并指定使用 secret my_secret_data
。
docker service create \
--name mysql \
--secret source=my_secret_data,target=mysql_root_password \
-e MYSQL_ROOT_PASSWORD_FILE="/run/secrets/mysql_root_password" \
mysql:latest
① source 指定容器使用 secret 后,secret 会被解密并存放到容器的文件系统中,默认位置为 /run/secrets/<secret_name>。--secret source=my_secret_data,target=mysql_root_password
的作用就是指定使用 secret my_secret_data
,然后把器解密后的内容保存到容器 /run/secrets/mysql_root_password
文件中,文件名称 mysql_root_password
由 target
指定。
② 环境变量 MYSQL_ROOT_PASSWORD_FILE
指定从 /run/secrets/mysql_root_password
中读取并设置 MySQL 的管理员密码。
这里大家可能有这么两个疑问:
1、问:在第一步创建 secret 时,不也是使用了明文吗?这跟在环境变量中直接指定密码有什么不同呢?
答:在我们的例子中创建 secret 和使用 secret 是分开完成的,其好处是将密码和容器解耦合。secret 可以由专人(比如管理员)创建,而运行容器的用户只需使用 secret 而不需要知道 secret 的内容。也就是说,例子中的这两个步骤可以由不同的人在不同的时间完成。
2、问:secret 是以文件的形式 mount 到容器中,容器怎么知道去哪里读取 secret 呢?
答:这需要 image 的支持。如果 image 希望它部署出来的容器能够从 secret 中读取数据,那么此 image 就应该提供一种方式,让用户能够指定 secret 的位置。最常用的方法就是通过环境变量,Docker 的很多官方 image 都是采用这种方式。比如 MySQL 镜像同时提供了 MYSQL_ROOT_PASSWORD
和 MYSQL_ROOT_PASSWORD_FILE
两个环境变量。用户可以用 MYSQL_ROOT_PASSWORD
显示地设置管理员密码,也可以通过 MYSQL_ROOT_PASSWORD_FILE
指定 secret 路径。
只有当 secret 被指定的 service 使用是,Docker 才会将解密后的 secret 以文件的形式 mount 到容器中,默认的路径为/run/secrets/<secret_name>
。例如在前面 MySQL 的例子中,我们可以在容器中查看 secret。
当容器停止运行,Docker 会 unmount secret,并从节点上清除。
十、Stack
什么是 stack ?
在回答这个问题之前我们先回忆一下前面部署 WordPress 应用的过程:
1、首先创建 secret。
2、然后创建 MySQL service,这是 WordPress 依赖的服务。
3、最后创建 WordPress service。
也就是说,这个应用包含了两个 service:MySQL 和 WordPress,它们之间有明确的依赖关系,必须先启动 MySQL。
为了保证这个依赖关系,我们控制了 docker secret
和 docker service
命令的执行顺序,只不过这个过程是手工完成的。
如果将前面 WordPress 用 stack 来定义,YAML 文件可以是这样:
YAML 是一种阅读性很强的文本格式,上面这个 stack 中定义了三种资源:service、secret 和 volume。
① services
定义了两个 service:db
和 wordpress
。
② secrets
定义了两个 secret:db_password
和 db_root_password
,在 service db
和 wordpress
的定义中引用了这两个 secret。
③ volumes
定义了一个 volume:db_data
,service db
使用了此 volume。
④ wordpress
通过 depends_on
指定自己依赖 db
这个 service。Docker 会保证当 db
正常运行后再启动 wordpress
。
可以在 YAML 中定义的元素远远不止这里看到的这几个,完整列表和使用方法可参考文档 https://docs.docker.com/compose/compose-file/
定义好了 stack YAML 文件,就可以通过 docker stack deploy
命令部署应用。
docker stack rm
会将 stack 相关的所以资源清除干净。
参考文档:https://www.cnblogs.com/CloudMan6/p/7834113.html