本系列分为以下文章。
在上一篇文章中,我们手动部署了服务的第一个版本以及一个单独的Mongo DB容器实例。 两者都(可能)在不同的服务器上运行。 Docker Swarm决定了在哪里运行我们的容器, Consul存储了有关服务IP和端口的信息以及其他有用的信息。 该数据用于将一项服务与另一项服务链接,并提供创建代理所需的nginx信息。
我们将继续我们离开的地方,并部署服务的第二个版本。 由于我们正在练习蓝/绿部署,因此第一个版本称为blue ,下一个版本为green 。 这次还会有一些其他的并发症。 第二次部署要复杂一些,因为还有很多事情要考虑,尤其是因为我们的目标是不造成停机。
设定
对于那些停止我们在上一篇文章中创建的VM( vagrant halt
)或关闭您的笔记本电脑的人,这里是如何快速恢复到以前的状态。 其余的人可以跳过本章。
vagrant up
vagrant ssh swarm-master
ansible-playbook /vagrant/ansible/infra.yml -i /vagrant/ansible/hosts/prod
export DOCKER_HOST=tcp://0.0.0.0:2375
docker start booksservice_db_1
docker start booksservice_blue_1
sudo consul-template -consul localhost:8500 -template "/data/nginx/templates/books-service-blue-upstream.conf.ctmpl:/data/nginx/upstreams/books-service.conf:docker kill -s HUP nginx" -once
通过运行以下命令,我们可以验证是否一切正常。
docker ps
curl http://10.100.199.200/api/v1/books | jq .
第一个命令应列出(除其他外)booksservice_blue_1和booksservice_db_1容器。 第二本应该检索我们之前插入的三本书的JSON响应。
有了这种偏僻,我们可以继续我们离开的地方。
运行服务容器的第二个版本
当第一个版本是蓝色时 ,这个版本将被称为绿色 。 我们将与上一个并行运行它,以避免任何停机时间。 一旦一切运行正常,并且我们确定新版本(绿色)可以正常运行,旧版本(蓝色)将停止。
cd /data/compose/config/books-service
docker-compose pull green
docker-compose rm -f green
docker-compose up -d green
docker ps | grep booksservice
目前,我们已经启动并运行了两个版本的服务(旧版本和新版本;蓝色和绿色)。 从最后一个命令( docker ps
)的输出中可以看出。 它应该显示在不同服务器上运行的两个服务(如果显示为容器数量最少的地方,则应显示相同)。
这是我们应该运行自动化测试的时刻。 我们没有为本文准备它们,因此我们将手动执行curl
命令以检查一切是否正常。
curl http://localhost:8500/v1/catalog/service/books-service-green | jq .
curl http://[IP]:[PORT]/api/v1/books | jq .
第一个命令查询Consul并返回与books-service-green服务相关的数据。 请确保将第二个命令( curl
)中的[IP]和[PORT]更改为从领事那里获得的值。
请记住,我们通过将请求直接发送到其IP和端口而不是发送给nginx服务的公共可用地址来测试该服务。 目前,两种服务都在运行,旧的(蓝色)可在http://10.100.199.200上向公众使用,而新的(绿色)目前仅对我们可用。 不久,我们将更改nginx并告诉它将所有请求重定向到新请求,从而将零停机时间归档。 现在一切似乎都按预期进行,现在是时候向公众发布新版本(绿色)了。
curl -X PUT -d 'green' http://localhost:8500/v1/kv/services/books-service/color
sudo consul-template -consul localhost:8500 -template "/data/nginx/templates/books-service-green-upstream.conf.ctmpl:/data/nginx/upstreams/books-service.conf:docker kill -s HUP nginx" -once
docker stop booksservice_blue_1
curl http://10.100.199.200/api/v1/books | jq .
docker ps -a | grep booksservice
首先,我们为books-service / color Consul键添加了一个新值(绿色)。 如果我们想知道当前正在运行什么颜色,可以将其用作将来的参考。 当我们达到将所有这些完全自动化的程度时,这些信息将派上用场。 然后,我们通过运行consul-template更新了nginx配置。 接下来, curl
命令是最后的测试部分,它验证可公开使用的服务(这次为绿色)是否按预期运行并且没有停机。 此测试应该是自动化的,但是为了简洁起见,本文跳过了测试自动化。 最后,我们正在检查docker ps -a
命令的输出。 它应显示booksservice_green_1为Up,而booksservice_blue_1显示为Exited 。
Ansible和Jenkins实现自动化
到目前为止,我们所做的一切都可以作为学习练习,但是在“现实世界”中,所有这些都应该自动化。 我们将使用Ansible作为编排工具来运行我们到目前为止所做的所有命令以及其他一些命令。 我们不会详细介绍Ansible剧本books-service.yml。 它可以与其他源代码一起在docker-swarm GitHub存储库中找到。 由于Ansible剧本遵循与我们运行的手动命令相同的逻辑,并且总的来说,其剧本非常易于阅读,因此希望您在没有进一步说明的情况下也不会遇到问题。 如果遇到问题,请咨询持续集成,交付和部署 。 它有很多关于Ansible的文章。 随时发送评论(如下),如果有任何疑问,请直接与我联系。
需要注意的重要一点是,不同的服务没有单独的角色。 当Ansible与微服务体系结构一起使用时,为每个服务单独承担角色可能很快就变得难以管理。 由于使用Docker易于标准化部署,因此只有一个角色称为service 。 该角色使用变量自定义不同微服务之间的差异。 例如,一个人可能使用数据库(例如books-service ),而其他人则不使用(例如books-fe )。 换句话说,每个服务都有一个单独的剧本,其中包含特定于服务的变量,并且它们都依赖于相同的角色。
例如,以下是books-service.yml剧本的定义。
- hosts: service
remote_user: vagrant
sudo: yes
vars:
- container_image: books-service
- container_name: books-service
- http_address: /api/v1/books
- has_db: true
roles:
- docker
- consul
- swarm
- nginx
- service
我们在vars部分下定义了服务特定的变量,并附带了此服务所需的所有角色 。 变量定义了镜像和Docker容器的名称,HTTP地址服务应在其上运行(用于提供nginx代理)以及是否具有数据库。 所有服务都依赖于相同的角色。 他们需要Docker , Consul , Swarm , nginx和服务 。 除了最后一个以外,其他所有东西都是依赖项。 服务角色定义了此服务以及我们正在运行的所有其他服务。 如果查看books-fe.yml剧本,您会注意到两者之间的唯一区别是变量。 请查看docker-swarm GitHub存储库中的源代码以了解更多信息。
现在,让我们运行books-service.yml剧本来部署我们服务的另一个版本。
ansible-playbook /vagrant/ansible/books-service.yml -i /vagrant/ansible/hosts/prod
做起来比我们现在做的要好得多。 一个命令可以处理所有事情。
工作正常吗? 我们可以像以前一样检查一下。
curl http://localhost:8500/v1/kv/services/books-service/color?raw
curl http://10.100.199.200/api/v1/books | jq .
docker ps -a | grep booksservice
我们可以看到当前的颜色是蓝色(以前是绿色),nginx正常工作,旧的(绿色)容器已停止,新的(蓝色)容器正在运行。
我们可以不运行单个命令就能做到吗? 这就是Jenkins(和类似工具)的用途。 如果您已经使用过詹金斯,那么您可能会认为您应该跳过这一部分。 请耐心等待,因为一旦设置完毕,我们将通过各种方法使自己从失败中恢复过来。
首先让我们确保Jenkins已启动并正在运行。
ansible-playbook /vagrant/ansible/jenkins.yml -i /vagrant/ansible/hosts/prod
像Ansible这样的编排工具的伟大之处在于,它们始终检查状态并仅在需要时采取行动。 如果詹金斯早已运转起来,那么安西布尔什么也没做。 另一方面,如果未部署或关闭它,则Ansible会采取相应措施并使其重新工作。
可以通过在您喜欢的浏览器中打开http://10.100.199.200:8080/来查看Jenkins。 在其他作业中,您应该看到将用于部署服务的新版本的books-service作业。
接下来,使用以下步骤创建一个新节点。
- 单击管理詹金斯 > 管理节点 > 新节点
- 将其命名为cd ,选择“ Dumb Slave” ,然后单击“ 确定”。
- 键入/ data / jenkins / slaves / cd作为远程根目录
- 输入10.100.199.200作为主机
- 点击“ 凭据”旁边的添加*。
- 使用vagrant作为用户名和密码 ,然后单击添加
- 点击保存
现在,我们可以运行图书服务工作。 在Jenkins主页上,单击books-service链接,然后单击“ 立即构建”按钮。 刷新页面,您将看到“ 构建历史记录”中的图标闪烁,直到完成运行为止。 完成后,将出现一个蓝色图标,指示一切正常运行。
运行docker ps
确认服务正在运行。
docker ps -a | grep booksservice
Jenkins作业配置中缺少的是“ 源代码管理”设置,该设置会在发生任何更改并启动部署时从存储库中提取代码。 毕竟,我们不想浪费时间在每次有人更改代码时按Build按钮。 由于这是在本地运行,因此无法演示SCM设置。 但是,您应该可以轻松地在线查找信息。
现在,让我们转到更有趣的事情上,看看如何从失败中恢复过来。
自我修复系统
出问题了怎么办? 例如,当一项服务或整个节点出现故障时会发生什么? 我们的系统应该能够从此类问题中恢复过来。 自我修复系统是一个涵盖代码体系结构,服务器设置,通知等的大主题。该主题至少应有整篇文章(可能还包括整本书),因此我们不会深入研究“ 自我修复”的概念,而只涉及最简单的情况。 我们将以一种服务停机的方式重新部署系统。 另一方面,如果整个节点停止工作,则该节点上的所有容器都将被转移到运行状况良好的容器中。 为此,我们需要Consul,Jenkins和Ansible。
这是我们要完成的流程。 我们需要一些东西来监控我们的服务健康状况 。 我们将为此使用Consul 。 如果其中之一没有每10秒响应一次请求,则将调用Jenkins作业。 反过来,Jenkins将运行Ansible ,以确保与该服务相关的所有内容都已启动并正在运行。 由于我们使用的是Docker Swarm ,如果导致服务不正常的原因是服务器关闭,则将选择运行状况良好的节点。 我们可以在没有Jenkins的情况下完成此操作,但是由于我们已经设置好了它,并且它提供了许多不错且易于配置的功能(即使在本示例中不使用它们),我们也会坚持使用。
在详细介绍之前,让我们来看一下它的作用。 我们将从停止服务开始。
docker ps | grep booksservice
docker stop booksservice_green_1
docker stop booksservice_blue_1
docker ps -a
目前,我不确定您是运行蓝色还是绿色版本,因此上述命令将尝试同时停止这两个版本。 第二个docker ps -a
命令用于验证服务确实已停止(状态应为Exited )。
现在我们停止了服务,领事将检测到该情况,因为它每10秒运行一次验证。 我们可以通过以下内容查看其日志。
cat /data/consul/logs/watchers.log
输出应类似于以下内容。
Consul watch request:
[{"Node":"swarm-master","CheckID":"service:books-service","Name":"Service 'books-service' check","Status":"critical","Notes":"","Output":"HTTP GET http://10.100.199.200/api/v1/books: 502 Bad Gateway Output: u003chtmlu003ernu003cheadu003eu003ctitleu003e502 Bad Gatewayu003c/titleu003eu003c/headu003ernu003cbody bgcolor="white"u003ernu003ccenteru003eu003ch1u003e502 Bad Gatewayu003c/h1u003eu003c/centeru003ernu003chru003eu003ccenteru003enginx/1.9.2u003c/centeru003ernu003c/bodyu003ernu003c/htmlu003ern","ServiceID":"books-service","ServiceName":"books-service"}]
>>> Service books-service is critical
Triggering Jenkins job http://10.100.199.200:8080/job/books-service/build
如果看不到相同的输出,请确保在停止服务和检查Consul日志之间经过了10秒。
领事发现有问题,并向http://10.100.199.200:8080/job/books-service/build发出了请求。 这又触发了我们手动运行的同一Jenkins作业,并执行了另一个部署。
您可以从Jenkins查看部署进度。 完成后,我们可以再次查看容器状态。
docker ps | grep booksservice
假设您停止了绿色版本,这一次您将看到booksservice_blue_1正在运行。
如果我们停止MongoDB甚至任何节点(例如swarm-node-01),都会发生相同的过程。 如果某些服务没有响应,Consul将触发相应的Jenkins作业并重复部署周期。 即使整个节点关闭,驻留在该节点上的服务也不会响应,并且该过程将重复进行。
这是如何运作的?
每次我们使用Ansible部署服务时(目前只有一个,但可以扩展到其中的任何数量),我们都确保使用有关该服务的信息来更新Consul配置。 让我们看一下books-service的配置。
cat /etc/consul.d/service-books-service.json
输出应如下。
{
"service": {
"name": "books-service",
"tags": ["service"],
"port": 80,
"address": "10.100.199.200",
"checks": [{
"id": "api",
"name": "HTTP on port 80",
"http": "http://10.100.199.200/api/v1/books",
"interval": "10s"
}]
}
}
它具有有关服务的信息。 请记住,与使用Consul Registrator存储的,具有运行服务的精确服务器和端口的信息不同,此信息是从用户的角度来看的。 换句话说,它指向我们的公共IP和端口(10.100.199.200),该端口由nginx处理,依次将请求重定向到实际服务所在的服务器。 原因是在这种特殊情况下,我们只关心整个服务永远不会中断。 即使我们不断部署蓝色和绿色发行版,其中之一也应该一直运行。
最后,有检查部分。 它告诉领事每10秒对http://10.100.199.200/api/v1/books执行一次http请求。 如果服务器返回除代码200外的任何内容,Consul将认为该服务无法正常工作,并启动“救援”程序。
每个服务应具有其自己的服务配置文件,并根据其具体情况检查部分。 检查的类型不同,但就我们而言, http正是在执行我们所需的工作。
接下来的是watchers.json配置文件。
cat /etc/consul.d/watchers.json
输出如下。
{
"watches": [
{
"type": "checks",
"state": "critical",
"handler": "/data/consul/scripts/redeploy_service.sh >>/data/consul/logs/watchers.log"
}
]
}
它告诉Consul监视类型检查的所有服务。 该过滤器是必需的,因为我们已经向Consul注册了服务,并且只希望检查指定的服务。 第二个过滤器是state 。 我们只希望检索处于临界状态的服务。 最后,如果Consul找到类型和状态都匹配的服务,它将运行handler中设置的redeploy_service.sh命令。 让我们来看看它。
cat /data/consul/scripts/redeploy_service.sh
输出如下。
#!/usr/bin/env bash
RED='33[0;31m'
NC='33[0;0m'
read -r JSON
echo "Consul watch request:"
echo "$JSON"
echo "$JSON" | jq -r '.[] | select(.CheckID | contains("service:")) | .ServiceName' | while read SERVICE_NAME
do
echo ""
echo -e ">>> ${RED}Service $SERVICE_NAME is critical${NC}"
echo ""
echo "Triggering Jenkins job http://10.100.199.200:8080/job/$SERVICE_NAME/build"
curl -X POST http://10.100.199.200:8080/job/$SERVICE_NAME/build
done
我不会详细介绍该脚本,但是会说它从Consul接收JSON,对其进行解析以找出失败的服务的名称,最后,向Jenkins发出请求以运行相应的作业并重新部署该服务。
未完待续
我们根据蓝/绿部署技术部署了多个版本的服务。 在部署过程中,服务绝不会中断。 我们通过Ansible使整个过程自动化,并由Jenkins运行。
我们引入了一些新的Consul功能,使我们可以监视服务的状态并采取措施使它们从故障中恢复过来。 在当前设置中,我们仍然不能保证零停机时间,因为Consul可能最多需要10秒才能检测到故障,而Jenkins / Ansible可能要花更多的时间来执行部署。
下一篇文章将进一步探讨如何将每个服务扩展到多个节点的方法,以便当一个实例出现故障时,至少还有一个正在运行。 这样,即使我们可以重新部署任何失败的服务,在此过程中,第二个实例仍在运行,并确保没有停机时间。
扩展个人服务一文中的故事继续。