您的位置:首页 > 运维架构 > Docker

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

2017-08-16 08:17 901 查看
在这一系列文章的第一篇中,我们分享了只用Docker时我们开发的初步的工作流,如何创建一个基础的构建和部署流水线。容器的部署方式不再是在登陆server的时候从内存中输入Docker命令。我们已经通过Jenkins server实现了镜像的自动化构建。我们使用脚本将Docker命令进行封装,将其存储到GitHub中并且设置版本。目前我们正采取措施,通过逐步改善现有过程来实现持续部署。然而,仍有一些痛点需要我们去解决。在这篇文章中,我们将看看如何使用Docker Compose 和 Ansible来改善此设计。

在部署镜像时,工程师需要登录到服务器,并从shell运行我们的Docker wrapper脚本。这不是很好的解决方法,因为它也需要开发者进行等待。没有任何一方会从在这种方式中获益(作为一个工程师,当你去做某件你很了解并且很容易自动化的事情时,你有多少次被打断了?)由于每一次部署都是通过操作者电脑中的SSH会话来执行的,因此部署过程是不可见的。

如果你对我们的部署脚本还有印象,你会发现它看起来像下面的代码段:



实际上,我们做的是将Docker run命令语句进行抽象,由此工程师将不需要知道每个图像成功运行时所需要的确切的参数。虽然这改善了必须全部记住并且手动输入所有Docker参数的现状,但同时也会带来新的问题:

每个容器的逻辑都存储在同一文件中,这使得对应用程序部署逻辑的更改更难追踪;

当开发者需要测试或者修改参数时,需要被迫理清脚本中的逻辑,而不是能够在某一特定的程序中轻松地阅读和修改参数。

在我们的工作流中,Docker Compose是一个更适合使用的工具,它同样可以将部署参数进行编码,并且在YAML文件中指定,此文件就是docker-compose.yml。Docker Compose不仅帮助我们解决了上面提到的难点,而且也可以使我们从社区未来的工作中获益。下面让我们理清部署脚本,并且为我们的JAVA程序示例创建一个Compose文件。首先,我们需要基于原来的部署逻辑创建一个docker-compose.yml文件:



现在,部署容器只需要在与docker-compose.yml文件相同目录下输入以下命令:

docker-compose up


它将根据compose文件中设置的参数启动一个容器。在compose文件中一个重要的变量是${VERSION} 。Docker Compose可以从当前的shell环境中插入compose文件里所列出的参数。我们可以通过简单地运行以下语句来设置参数:

VERSION=1.0.0 docker-compose up


它将从我们的私有镜像仓库挑出标记1.0.0的镜像,以此启动java-service-1程序。如果没有设置VERSION变量,Docker Compose将产生一条警告信息,并且用空字符串代替变量值,由此,具有最新版本标签的镜像将会被挑出。因此,正确地设置变量是相当重要的。

作为开发过程的一部分,我们希望开发人员能够在本地建立服务并且测试他们的服务。然而,由于docker-compose.yml指向私有镜像仓库的镜像,运行docker-compose将从最近构建的镜像中开启服务而不是从本地资源中开启。理想情况下,开发者可以通过运行以下代码使用典型的docker-compose工作流:



Docker Compose能在不修改docker-compose.yml文件的情况下,让我们做到这一点。我们可以使用多个文件来覆盖我们在本地测试中想要改变的任何参数。在docker-compose.override.yml中,我们指定一个key而不是一个镜像,并且移除了对VERSION变量的需求。由于这是一个覆盖文件,我们不需要复制任何额外的设置,如端口设置:



使用Docker Compose而非部署脚本之后,我们可以:

在源代码中存储每个compose文件,这与Dockerfile类似;

不再需要复杂的部署脚本;

允许开发人员在本地轻松地测试并修改应用程序。

现在我们有了java-service-1程序的compose文件,我们可以将它从我们的部署脚本中删除,因此文件组织与下面的结构类似:



此时,我们仍然没有解决镜像构建和部署之间的问题。在docker-compose.yml文件中包含了所有的部署逻辑,但是它如何在环境中运行直至结束的呢?正好现在我们在运行与UNIX和TPC socket相关的Docker守护进程,是时候讨论一些与安全有关的问题了。

我们的情况是,工程师登录到服务器上,手动运行每个服务器所需容器的部署脚本。默认情况下,当在局部运行Docker命令时,它将使用UNIX socket /var/run/docker.sock连接Docker守护进程;或者让守护进程监听TCP socket,这允许用户远程连接到每个Docker守护进程,使得工程师能够像登录到主机一样运行命令。这为连接方式提供了更大的灵活性,但是没有考虑到一些开销和安全问题:

通过网络连接增加了安全隐患;

增加了对于基于主机或者基于网络的ACLs需求;

保护守护进程需要分布式CA和客户端认证。

另一种可能的方法是不使用基于UNIX socket的方式运行Docker守护进程,而使用SSH来运行命令。已经建立的ACLs将保护SSH端口,并且它只允许通过SSH授权的特定的用户才能使用Docker守护进程。虽然这不是最简洁的方法,但是它有助于保持较低的运行开销,并且使安全隐患降到最低。这点是非常重要的,尤其是对于细粒度的稀疏的任务队列而言。

为了有利于通过SSH运行Docker命令,我们可以使用Ansible——一个流行的编排和配置管理工具。它是无代理的,并且允许通过SSH连接运行“剧本”(服务器任务集合)。一个运行docker-compose命令的简单的剧本如下所示:



如果你对Ansible没有过多了解,你也许可以通过上面的剧本大致了解到我们想做什么。它们按顺序一步步执行,具体如下所示:

Ansible将通过SSH连接到目标服务器(允许通过使用DESTINATION变量来指定主机)

在每个服务器中,Ansible会通过执行shell命令登录到公司私有的镜像仓库

Ansible将位于Jenkins(运行ansible剧本的服务器)中的docker-compose.yml文件复制到每个目标服务器中的/tmp/docker-compose.yml下

在每个目标服务器中运行docker-compose命令

通过删除远程的/tmp/docker-compose.yml文件进行清理

一个shell脚本可以被运用在同一个事件中。然而在Ansible中,我们将很容易的使任务并行化并且得到经过良好测试的模块,通过使Ansible与新的部署剧本相结合,我们可以远程启动容器,相较于工程师登录到主机、人工运行命令,这是一个重要的进步。为了在部署过程和状态中提供更大的可视性,我们将建立Jenkins任务来运行Ansible代码。通过使用Jenkins,在未来我们可以轻松地将构建和部署任务集成起来,从而得到额外的好处。

Jenkins任务需要两个参数:目标主机(传递给剧本中的DESTINATION变量)和部署镜像的版本(在docker-compose.yml文件中插入VERSION变量)。大多数任务的构建部分是一个shell构建器,它将试图找到程序中的docker-compose.yml文件,然后通过传递变量(用-e)到剧本中,运行ansible-playbook命令:



虽然看起来我们似乎只对工作流做了微小的变化,但是我们正一步一步地向构建一个持续部署模型迈进:

部署是可以被审查的。我们使用日志来记录输出什么、何时输出、以及哪些主机是目标主机等信息,这一切都归功于Jenkins。

程序部署逻辑已经从一个单一的脚本分散到存储在程序源代码中的单独的docker-compose.yml文件中,这意味着我们可以轻松地通过git更改程序部署逻辑。在程序源文件或者部署文件发生变化时,我们也可以容易地进行构建和部署。

虽然这些改进解决了某些问题,但是它们所带来的新的问题也成为了焦点:

哪个容器的哪个版本会被部署到何地?

容器在被部署后会处于哪种状态?

我们如何确定哪个主机成为程序的目标主机?

在这一系列接下来的文章中,我们将探讨怎样运行Rancher以及使用它的原因,尤其是它如何解决上述的问题。与此同时,我们也讨论它在业务和开发团队中所起到的意想不到的桥梁作用。

原文来源:Rancher Labs

9月27日,北京海航万豪酒店,容器技术大会Container Day 2017即将举行。

CloudStack之父、海航科技技术总监、华为PaaS部门部长、恒丰银行科技部总经理、阿里云PaaS工程总监、民生保险CIO······均已加入豪华讲师套餐!

11家已容器落地企业,15位真·云计算大咖,13场纯·技术演讲,结合实战场景,聚焦落地经验。免费参会+超高规格,详细议程及注册链接请戳

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: