如何使用Docker、Docker-Compose和Rancher搭建部署Pipeline(四)

on 9月 1, 2017

重磅系列干货,如何使用Docker、Docker Compose和Rancher搭建deployment pipeline。
本文为最后一篇,讨论如何实现consul的服务发现。

在这篇文章中,我们将讨论如何用Rancher实现consul的服务发现。

如果你还没有准备好,推荐你阅读本系列中先前的文章:
第一篇:CI /CD和Docker入门
第二篇:使部署逻辑向使用Docker Compose更进一步
第三篇:借力Rancher完成容器编排

在这构建部署流水线系列的最后一篇文章中,我们将探讨在转换到Rancher进行集群调度时面临的一些挑战。在之前的文章中,我们通过使用Rancher执行调度,让运维人员无须再负责选择每一次容器运行的位置。要使用这个新方案,我们必须让环境的其他部分知道调度程序放置这些服务的位置,以及如何访问它们。我们还将讨论如何使用标签来操作调度程序,以调整容器放置位置,并避免端口绑定冲突。最后,我们将通过利用Rancher的回滚功能优化我们的升级过程。

在引入Rancher之前,我们的环境是一个相当静态的环境。我们总是将容器部署到相同的主机上,而部署到不同的主机则意味着我们需要更新一些配置文件以反映新位置。例如,如果我们要添加’java-service-1’应用程序的一个附加实例,我们还需要更新load balancer以指向附加实例的IP。使用调度器让我们无法预测容器部署的位置,并且我们需要动态配置环境,使其能自动适应变化。为此,我们需要使用服务注册和服务发现。

服务注册表为我们提供了应用程序在环境中的位置的单一来源。和硬编码服务位置不同,我们的应用程序可以通过API查询服务注册表,并在我们的环境发生变化时自动重新配置。Rancher使用Rancher的DNS和元数据服务提供了开箱即用的服务发现。然而,混合使用Docker和非Docker应用程序时,我们不能完全依赖Rancher来处理服务发现。我们需要一个独立的工具来跟踪我们所有服务的位置,consul就符合这个要求。

我们不会详细说明如何在您的环境中设置Consul,但是,我们将简要描述我们在ABC公司使用Consul的方式。在每个环境中,我们都有一个部署为容器的Consul集群。我们在环境中的每个主机上都部署一个Consul代理,如果主机正在运行Docker,我们还会部署一个注册器容器。注册器监视每个守护进程的Docker事件API,并在生命周期事件期间自动更新Consul。例如,在新容器被部署后,注册器会自动在Consul中注册该服务。当容器被删除时,注册器撤销它的注册。

Consul服务列表

在Consul中注册所有服务后,我们可以在负载均衡器中运行consul-template,根据Consul中存储的服务数据动态填充上游列表。对于我们的NGINX负载均衡器,我们可以创建一个模板来填充’java-service-1’应用程序的后端:

# upstreams.conf
upstream java-service-1 { 
{{range _, $element := service "java-service-1"}}    
       server {{.Address}}:{{.Port}}; 
{{else}}     
       server 127.0.0.1:65535; # force a 502{{end}} }

此模板在Consul中查找注册为“java-service-1”的服务的列表。然后它将循环该列表,添加具有该特定应用程序实例的IP地址和端口的服务线。如果在Consul中没有注册任何“java-service-1”应用程序,我们默认抛出502以避免NGINX中的错误。

我们可以在守护进程模式下运行consul-template,使其监控Consul的更改,在发生更改时重新渲染模板,然后重新加载NGINX以应用新配置。

TEMPLATE_FILE=/etc/nginx/upstreams.conf.tmpl
RELOAD_CMD=/usr/sbin/nginx -s reload 
consul-template -consul consul.stage.abc.net:8500 \     
       -template "${TEMPLATE_FILE}:${TEMPLATE_FILE//.tmpl/}:${RELOAD_CMD}"

通过使用我们的负载均衡器设置来动态地改变其余的环境变化,我们可以完全依赖Rancher调度器来做出我们的服务应该在哪里运行的复杂的决定。但是,我们的“java-service-1”应用程序在Docker主机上绑定TCP端口8080,如果在同一主机上调度了多个应用程序容器,则会导致端口绑定冲突并最终失败。为了避免这种情况,我们可以通过调度规则来操作调度器。

通过在docker-compose.yml文件中使用容器标签来提出条件,是Rancher给我们的一种操作调度器的方法。条件可以包括亲和规则、否定、至“软”强制(意味着尽可能地避免)。在我们使用’java-service-1’应用程序的情况下,我们知道在给定时间只有一个容器可以在主机上运行,因此我们可以基于容器名称设置反关联性规则。这将使调度程序查找一个未运行名称为“java-service-1”的容器的Docker主机。我们的docker-compose.yml文件看起来像下面这样:

java-service-1:   
   image: registry.abc.net/java-service-1:${VERSION}   
   container_name: java-service-1   
   ports:    
         - 8080:8080   
    labels:     
         io.rancher.scheduler.affinity:container_label_ne: io.rancher.stack_service.name=java-service-1

注意“标签”键的引入。所有调度规则都作为标签被添加。标签可以被添加到Docker主机和容器。当我们在Rancher注册我们的主机时,我们可以将它们与标签关联,以后就可以切断调度部署。例如,如果我们有一组使用SSD驱动器进行存储优化的Docker主机,我们可以添加主机标签storage=ssd。

Rancher主机标签

需要利用优化存储主机的容器可以添加标签来强制调度程序仅在匹配的主机上部署它们。我们将更新我们的“java-service-1”应用程序,以便只部署在存储优化的主机上:

java-service-1:   
    image: registry.abc.net/java-service-1:${VERSION}   
    container_name: java-service-1   
    ports:    
         - 8080:8080   
    labels:     
         io.rancher.scheduler.affinity:container_label_ne: io.rancher.stack_service.name=java-service-1     
         io.rancher.scheduler.affinity:host_label: storage=ssd

通过使用标签,我们可以根据所需的容量,而不是个别主机运行特定的容器集,来精细地调整我们的应用程序部署。切换到Rancher进行集群调度,即使您仍然有必须在特定主机上运行的应用程序。

最后,我们可以利用Rancher的回滚功能优化我们的服务升级。在我们的部署工作流中,通过调用rancher-compose来指示Rancher在该服务堆栈上执行升级以部署服务。升级过程大致如下:

  1. 通过拉取一个新的镜像来启动升级
  2. 逐一地,现有容器被停止并且新容器被启动
  3. 部署程序登录到UI并选择“完成升级”时,升级完成,
  4. 已停止的旧服务容器被删除

Rancher升级

当给定服务的部署非常少时,此工作流就好了。但是,当某个服务处于“升级”状态(在部署者选择“完成升级”之前)时,在执行“完成升级”或是“回滚”操作之前,你都不能对它进行任何新的升级”。rancher-compose实用程序让我们可以选择以编程方式选择要执行的操作,以部署程序者的身份执行操作。例如,如果您对服务进行自动测试,则可以在rancher-compose升级返回后调用此类测试。根据这些测试的状态,rancher-compose可以被再次调用,这次我们告诉堆栈“完成升级”或“回滚”。我们部署Jenkins作业的一个原始示例可能如下:

# for the full job, see part 3 of this series
/usr/local/bin/rancher-compose --verbose \   
    -f ${docker_dir}/docker-compose.yml \   
    -r ${docker_dir}/rancher-compose.yml \   
    up -d --upgrade 
JAVA_SERVICE_1_URL=http://java-service-1.stage.abc.net:8080/api/v1/status 
if curl -s ${JAVA_SERVICE_1_URL} | grep -q "OK"; then   
   
# looks good, confirm or "finish" the upgrade   
    /usr/local/bin/rancher-compose --verbose \    
         -f ${docker_dir}/docker-compose.yml \     
         -r ${docker_dir}/rancher-compose.yml \     
         up --confirm-upgrade 
else   

     # looks like there's an error, rollback the containers   
     # to the previously deployed version   
     /usr/local/bin/rancher-compose --verbose \     
        -f ${docker_dir}/docker-compose.yml \     
        -r ${docker_dir}/rancher-compose.yml \     
        up --rollback 
fi

这个逻辑将调用我们的应用程序端点来执行简单的状态检查。如果输出显示的是‘OK’,那么我们完成升级,否则我们需要回滚到以前部署的版本。如果您没有自动测试,另一个选择是简单地总是完成或“确认”升级。

# for the full job, see part 3 of this series
/usr/local/bin/rancher-compose --verbose \   
    -f ${docker_dir}/docker-compose.yml \   
    -r ${docker_dir}/rancher-compose.yml \   
    up -d --upgrade --confirm-upgrade

如果不久以后,您确定需要回滚,就使用相同的部署作业简单地重新部署以前的版本。这确实不像Rancher的升级和回滚功能那么友好,但它通过使堆栈不处于“升级”的状态来解锁将来的升级。

当服务在Rancher中回滚时,容器将被重新部署到以前的版本。当使用通用标记如“latest”或“master”部署服务时,可能会出现意外的后果。例如,让我们假设’java-service-1’应用程序以前被部署了标签’latest’。对图像进行更改,推送到注册表,Docker标签“latest”被更新为指向此新映像我们使用标签“latest”继续升级,在测试后决定应用程序需要回滚。使用Rancher滚动堆栈仍然会重新部署最新的映像,因为标签“latest”尚未被更新为指向上一个映像。回滚可以在纯技术术语中实现,但是部署最近的工作副本的预期效果完全无法实现。在ABC公司,我们通过始终使用与应用程序版本相关的特定标记来避免这种情况。因此,不要使用标记latest”部署我们的“java-service-1”应用程序,我们可以使用版本标签“1.0.1-22-7e56158”。这保证回滚将始终指向我们的应用程序在环境中的最新工作部署。

我们希望我们分享的经验对你们有所帮助。这有助于我们有条不紊地采用Docker,稳步改进我们的流程,并让我们的团队能熟悉这些概念。对更自动化的部署工作流进行增量更改,使组织能够更快地实现自动化的优势,部署团队可以更加务实地决定他们在流水线中需要什么。我们的经历证明Rancher在可行性、自动化、甚至团队协作方面都是成功的。我们希望分享这些我们在Docker应用过程中获得的经验教训将有助于您自己的应用过程。

Tags:

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*