本文是有关组织持续集成 /持续交付过程的文章的延续,该过程使适用于基于InterSystems平台的解决方案的应用程序的组装,测试和交付自动化。
考虑以下主题:
- 货柜101
- 处于软件开发周期不同阶段的容器
- 容器连续交付
货柜101
关于容器和容器化的文章和书籍很多,因此在这里我将做一个简短的介绍,但是,引言并不是最终的。 因此,让我们开始吧。
从技术上讲,容器是一种虚拟化方法,其中操作系统的内核支持几个隔离的用户空间实例(容器),而不是一个。 看起来像这样:

重要的是要注意容器不是虚拟机,这是一篇有关它们之间差异的好文章 。
集装箱的好处
使用容器有几个好处:
可携性
容器包含应用程序以及所有依赖项。 这使得在各种环境(例如物理服务器,虚拟机,测试环境和产品环境以及云)中运行应用程序变得容易。
此外,可移植性还包括以下事实:在Docker映像组装好并且可以正常工作之后,如果Docker在此工作,即可以在任何地方工作。 在Windows,Linux和MacOS服务器上。
实效
使用虚拟机应用程序时,您是否真的需要OS进程,系统程序等? 通常,不,只有您的申请过程很有趣。 容器恰恰提供了这一点:只有明显需要的那些过程才在容器中启动,仅此而已。 因为容器不需要单独的操作系统,所以它们使用的资源更少。 虚拟机通常占用数GB,而一个容器可以小到几兆字节,这使您可以在单个服务器上运行比虚拟机更多的容器。
由于容器具有更高的服务器利用率,因此需要较少的硬件,从而降低了成本。
隔离度
容器将应用程序与所有其他进程隔离开来,尽管几个容器可以在同一台服务器上运行,但是它们可以彼此完全独立。 容器之间的任何交互都必须明确声明。 如果一个容器发生故障,则不会影响其他容器,因此可以快速重新启动。 这种隔离也提高了安全性。 例如,利用主机上的Web服务器漏洞可以使攻击者可以访问整个服务器,但是对于容器,攻击者仅可以访问Web服务器容器。
轻便
由于这些容器不需要单独的操作系统,因此可以在几秒钟内启动,停止或重新启动它们,这将加快所有相关过程的速度,包括持续集成过程。 您可以开始更快地开发,而不会浪费时间来设置环境。
不变性
不可变的基础结构由不可变的组件组成,这些组件会为每个部署替换,并且不会更新。 一致性可以减少不一致性,并允许您轻松,快速地在应用程序的不同状态之间复制和移动。 关于不变性的更多信息 。
新功能
所有这些好处使您能够以新方式管理基础架构和应用程序。
编排
随着时间的流逝,虚拟机和服务器通常会获得“个性”,这会导致将来出现许多普遍不愉快的意外情况。 解决此问题的一种方法是“ 基础结构即代码 (IoC)”-使用具有版本控制系统的描述性模型进行基础结构管理。
使用IoC时,无论环境的初始状态如何,环境部署团队始终将目标环境带入相同的配置。 这可以通过自动设置现有环境或从头开始重新创建环境来实现。
开发人员使用IoC可以更改环境描述。 随后,将目标环境修改为新状态。 如果您需要在星期三进行更改,则会对其描述进行编辑。
使用容器可以轻松完成所有这些工作。 关闭容器并启动一个新容器需要花费几秒钟,而分配新虚拟机则需要花费几分钟。
缩放比例
编排工具还可以根据当前负载提供水平缩放。 可以运行当前所需数量的容器,并相应地扩展应用程序。 所有这些还降低了应用程序的成本。
处于软件生命周期不同阶段的容器
考虑在软件生命周期的各个阶段中容器的好处。

发展历程
最重要的优点是易于开始开发。 安装Docker之后 ,运行两个命令就足够了: docker pull
加载映像,而docker run
启动它。 在应用程序构建阶段,所有依赖关系都已解决。
侦错
所有环境都是一致的,并且它们的定义存在;此外,易于部署必要的环境。 足以使docker pull
所需的容器并运行它。
测试/质量检查
在发生错误的情况下,问题环境和重现错误的条件可以随容器一起转移。 所有基础架构更改均已“记录在案”。 变量的数量在减少-库,框架,操作系统的版本...可以运行多个容器来并行化测试。
派送
使用容器可以使您一次构建,除了使用容器之外,还需要高度自动化的组装和部署过程。 由于额外的隔离,应用程序的容器交付可以更安全。
持续交付
让我们从理论转向实践。 这是我们的组装和交付自动化解决方案的总体视图:

可以分为三个主要阶段:
组装方式
在上一篇文章中,程序集是增量的-我们考虑了当前环境与新代码库之间的差异,并更改了我们的环境,使其与新代码库相对应。 使用容器,每个组装都是完整的。 构建结果是可以在任何地方运行的Docker映像。
派送
我们的映像经过编译和测试后,将被上传到Docker Registry,这是一个用于托管Docker Image的专用应用程序。 在那里,他可以用相同的名称(标签)替换先前的图像。 例如,由于对master分支的新提交,我们组装了一个新映像( MyProject/MyApp:master
),如果通过了测试,我们可以在Docker注册表中更新该映像,所有下载MyProject/MyApp:master
都将获得一个新版本。
发射
最后,必须启动映像。 CD系统(如GitLab)可以直接或在专门的协调器的帮助下进行管理,但是过程通常是相同的-启动某些映像,定期检查性能,如果有新版本可用,则进行更新。
请查看网络研讨会,以解释这些步骤。
或者,就提交而言:

在我们的持续交付配置中,我们:
- 将代码提交到GitLab存储库
- 我们收集图像
- 测试它
- 在我们的Docker Registry中发布新映像
- 从Docker注册表将旧容器更新为新版本
为此,我们需要:
- 码头工人
- Docker注册表
- 注册域(可选,但可取)
- GUI工具(可选)
码头工人
首先,我们需要启动Docker。 我建议从具有通用Linux版本(例如Ubuntu,RHEL或Suse)的单个服务器开始。 我不建议从CoreOS,RancherOS等发行版开始-它们不是针对初学者的。 切记将存储驱动程序切换到devicemapper 。
如果我们谈论大规模部署,那么使用诸如Kubernetes,Rancher或Swarm之类的编排工具,您可以使大多数任务自动化,但我们不会讨论它们(至少在本文的框架中)。
Docker注册表
这是我们需要运行的第一个容器,它是一个独立的应用程序,允许我们存储和分发Docker映像。 如果需要,您需要使用Docker Registry:
- 控制图像的存储位置
- 拥有图像分发服务器
- 将图像存储和分发集成到开发过程中
这是有关启动和配置Docker Registry的文档 。
连接Docker Registry和GitLab
要将Docker Registry连接到GitLab,您需要运行具有HTTPS支持的Docker Registry。 我使用“让我们加密”来获取证书,然后按照此说明获取证书。 在确认可以通过HTTPS访问Docker Registry(您可以在浏览器中检查它)之后,请按照以下说明将Docker Registry连接到GitLab。 这些说明根据您的GitLab安装和所需的配置而有所不同。 就我而言,设置是将Docker注册表证书和密钥添加到/etc/gitlab/ssl
,并将这些行添加到/etc/gitlab/gitlab.rb
:
registry_external_url 'https://docker.domain.com' gitlab_rails ['registry_api_url'] = "https://docker.domain.com"
重新配置GitLab之后 ,将出现一个新的注册表选项卡,其中提供了有关如何正确命名所创建图像的信息,以便它们出现在此处。

域
在我们的持续交付配置中,我们将自动为每个分支创建一个映像,如果该映像通过测试,它将在Docker Registry中发布并自动启动,因此我们的应用程序将从所有分支自动部署,例如:
<featureName>.docker.domain.com
多个<featureName>.docker.domain.com
分支- 在
master.docker.domain.com
测试版本 preprod.docker.domain.com
版本位于preprod.docker.domain.com
- 产品版本位于
prod.docker.domain.com
为此,我们需要一个域名和通配符DNS记录,该记录* .docker.domain.com
请求重定向到* .docker.domain.com
的IP地址。 另外,您可以使用各种端口。
Nginx的
由于我们有多个环境,因此我们需要自动将对子域的请求重定向到正确的容器。 为此,我们可以将Nginx用作反向代理。 这是一个指南 。
GUI工具
要开始使用容器,可以使用命令行或图形界面之一。 有很多可用的,例如:
- 牧场主
- 微bad
- 烤瓷器
- 简单的docker ui
- ...
它们允许您创建容器并通过GUI而不是CLI来管理它们。 这是Rancher的样子:

Gitlab亚军
和以前一样,要在其他服务器上运行脚本,我们需要安装GitLab运行程序。 在上一篇文章中详细描述了这个问题。
请注意,您需要使用执行程序Shell,而不是Docker。 当您需要映像中的某些内容时(例如,在Java容器中创建Android应用程序时,并且仅需要apk),可以使用Executor Docker。 在我们的例子中,工件是整个容器,这需要执行程序外壳程序。
连续交付配置
现在已经配置了所有必需的组件,您可以开始创建连续交付配置。
组装方式
首先,我们需要组装一个图像。
与往常一样,我们的代码存储在gitlab-ci.yml
中的CD配置库中,但是除此之外(为了提高安全性),我们还将在构建服务器上存储与映像相关的几个文件。
Gitlab.xml
包含CD的回调代码。 它是在上一篇文章中开发的,可以在GitHub上找到 。 这是一个用于下载代码,运行各种回调和测试代码的小型库。 最好使用git子模块来包含此项目或您的存储库中的类似内容。 子模块更好,因为使它们保持最新状态更加容易。 另一种选择是在GitLab上创建一个发行版,并在构建时已经使用ADD命令下载它。
虹膜键
许可证密钥。 它可以在容器组装期间加载,而不存储在服务器上。 将密钥存储在存储库中是不安全的。 您可以在WRC上获得试用密钥,也可以尝试InterSystems IRIS Experience 。
pwd.txt
包含默认密码的文件。 同样,将密码存储在存储库中是相当不安全的。
load_ci.script
脚本:
- 在InterSystems IRIS中包括OS身份验证
- 加载GitLab.xml
- 初始化GitLab回调设置
- 加载代码
set sc = ##Class(Security.System).Get("SYSTEM",.Properties) write:('sc) $System.Status.GetErrorText(sc) set AutheEnabled = Properties("AutheEnabled") set AutheEnabled = $ZBOOLEAN(+AutheEnabled,16,7) set Properties("AutheEnabled") = AutheEnabled set sc = ##Class(Security.System).Modify("SYSTEM",.Properties) write:('sc) $System.Status.GetErrorText(sc) zn "USER" do ##class(%SYSTEM.OBJ).Load(##class(%File).ManagerDirectory() _ "GitLab.xml","cdk") do ##class(isc.git.Settings).setSetting("hooks", "MyApp/Hooks/") do ##class(isc.git.Settings).setSetting("tests", "MyApp/Tests/") do ##class(isc.git.GitLab).load() halt
请注意,第一行故意留为空白。 如果此初始脚本始终相同,则可以将其保存到存储库中。
gitlab-ci.yml
现在,让我们继续进行连续交付配置:
build image: stage: build tags: - test script: - cp -r /InterSystems/mount ci - cd ci - echo 'SuperUser' | cat - pwd.txt load_ci.script > temp.txt - mv temp.txt load_ci.script - cd .. - docker build --build-arg CI_PROJECT_DIR=$CI_PROJECT_DIR -t docker.domain.com/test/docker:$CI_COMMIT_REF_NAME .
这是怎么回事
首先,由于图像组装过程只能访问基本目录的子目录(在本例中为存储库的根目录),因此需要将“ secret”目录(具有GitLab.xml
, iris.key
, pwd.txt
和load_ci.skript
) load_ci.skript
到克隆的存储库。
此外,访问终端需要输入用户名/密码,因此我们将它们添加到load_ci.script
(为此,我们需要在load_ci.script
的开头留空行)。
最后,我们创建一个Docker映像并将其命名为: docker.domain.com/test/docker:$CI_COMMIT_REF_NAME
: docker.domain.com/test/docker:$CI_COMMIT_REF_NAME
其中$CI_COMMIT_REF_NAME
是分支的名称。 请注意:图像标签的第一部分必须与GitLab中存储库的名称匹配,以便可以在“注册表”选项卡上看到它(此处提供了正确标记的更多完整说明)。
Docker文件
Docker Image是使用Dockerfile创建的,它是:
FROM docker.intersystems.com/intersystems/iris:2018.1.1.613.0 ENV SRC_DIR=/tmp/src ENV CI_DIR=$SRC_DIR/ci ENV CI_PROJECT_DIR=$SRC_DIR COPY ./ $SRC_DIR RUN cp $CI_DIR/iris.key $ISC_PACKAGE_INSTALLDIR/mgr/ \ && cp $CI_DIR/GitLab.xml $ISC_PACKAGE_INSTALLDIR/mgr/ \ && $ISC_PACKAGE_INSTALLDIR/dev/Cloud/ICM/changePassword.sh $CI_DIR/pwd.txt \ && iris start $ISC_PACKAGE_INSTANCENAME \ && irissession $ISC_PACKAGE_INSTANCENAME -U%SYS < $CI_DIR/load_ci.script \ && iris stop $ISC_PACKAGE_INSTANCENAME quietly
执行以下操作:
- 我们以InterSystems IRIS的形象为基础。 它应该在您的Docker Registry中。 如果您以前从未使用过Docker,请尝试First Look:Docker ,它描述了如何获取InterSystems IRIS映像,将其添加到Docker Registry中并手动启动它。
- 首先,将我们的存储库(和“秘密”目录)复制到容器内。
- 将许可证密钥和
GitLab.xml
复制到mgr
目录。 - 将密码更改为
pwd.txt
的值。 请注意,在此操作过程中pwd.txt
被删除。 - 启动InterSystems IRIS。
load_ci.script
被load_ci.script
。- InterSystems IRIS停止。
这是部分构建日志 Running with gitlab-runner 10.6.0 (a3543a27) on docker 7b21e0c4 Using Shell executor... Running on docker... Fetching changes... Removing ci/ Removing temp.txt HEAD is now at 5ef9904 Build load_ci.script From http://gitlab.eduard.win/test/docker 5ef9904..9753a8d master -> origin/master Checking out 9753a8db as master... Skipping Git submodules setup $ cp -r /InterSystems/mount ci $ cd ci $ echo 'SuperUser' | cat - pwd.txt load_ci.script > temp.txt $ mv temp.txt load_ci.script $ cd .. $ docker build --build-arg CI_PROJECT_DIR=$CI_PROJECT_DIR -t docker.eduard.win/test/docker:$CI_COMMIT_REF_NAME . Sending build context to Docker daemon 401.4kB Step 1/6 : FROM docker.intersystems.com/intersystems/iris:2018.1.1.613.0 ---> cd2e53e7f850 Step 2/6 : ENV SRC_DIR=/tmp/src ---> Using cache ---> 68ba1cb00aff Step 3/6 : ENV CI_DIR=$SRC_DIR/ci ---> Using cache ---> 6784c34a9ee6 Step 4/6 : ENV CI_PROJECT_DIR=$SRC_DIR ---> Using cache ---> 3757fa88a28a Step 5/6 : COPY ./ $SRC_DIR ---> 5515e13741b0 Step 6/6 : RUN cp $CI_DIR/iris.key $ISC_PACKAGE_INSTALLDIR/mgr/ && cp $CI_DIR/GitLab.xml $ISC_PACKAGE_INSTALLDIR/mgr/ && $ISC_PACKAGE_INSTALLDIR/dev/Cloud/ICM/changePassword.sh $CI_DIR/pwd.txt && iris start $ISC_PACKAGE_INSTANCENAME && irissession $ISC_PACKAGE_INSTANCENAME -U%SYS < $CI_DIR/load_ci.script && iris stop $ISC_PACKAGE_INSTANCENAME quietly ---> Running in 86526183cf7c . Waited 1 seconds for InterSystems IRIS to start This copy of InterSystems IRIS has been licensed for use exclusively by: ISC Internal Container Sharding Copyright (c) 1986-2018 by InterSystems Corporation Any other use is a violation of your license agreement %SYS> 1 %SYS> Using 'iris.cpf' configuration file This copy of InterSystems IRIS has been licensed for use exclusively by: ISC Internal Container Sharding Copyright (c) 1986-2018 by InterSystems Corporation Any other use is a violation of your license agreement 1 alert(s) during startup. See messages.log for details. Starting IRIS Node: 39702b122ab6, Instance: IRIS Username: Password: Load started on 04/06/2018 17:38:21 Loading file /usr/irissys/mgr/GitLab.xml as xml Load finished successfully. USER> USER> [2018-04-06 17:38:22.017] Running init hooks: before [2018-04-06 17:38:22.017] Importing hooks dir /tmp/src/MyApp/Hooks/ [2018-04-06 17:38:22.374] Executing hook class: MyApp.Hooks.Global [2018-04-06 17:38:22.375] Executing hook class: MyApp.Hooks.Local [2018-04-06 17:38:22.375] Importing dir /tmp/src/ Loading file /tmp/src/MyApp/Tests/TestSuite.cls as udl Compilation started on 04/06/2018 17:38:22 with qualifiers 'c' Compilation finished successfully in 0.194s. Load finished successfully. [2018-04-06 17:38:22.876] Running init hooks: after [2018-04-06 17:38:22.878] Executing hook class: MyApp.Hooks.Local [2018-04-06 17:38:22.921] Executing hook class: MyApp.Hooks.Global Removing intermediate container 39702b122ab6 ---> dea6b2123165 [Warning] One or more build-args [CI_PROJECT_DIR] were not consumed Successfully built dea6b2123165 Successfully tagged docker.domain.com/test/docker:master Job succeeded
发射
我们有一个图像,运行它。 对于要素分支,您可以简单地销毁旧容器并启动一个新容器。 对于产品环境,如果测试成功通过,我们可以先启动临时容器,然后更换中型容器。
首先,删除旧容器的脚本。
destroy old: stage: destroy tags: - test script: - docker stop iris-$CI_COMMIT_REF_NAME || true - docker rm -f iris-$CI_COMMIT_REF_NAME || true
该脚本会破坏正在运行的容器并始终成功(默认情况下,Docker尝试停止/删除不存在的容器时会返回错误)。
之后,我们启动一个新容器并将其注册为环境。
run image: stage: run environment: name: $CI_COMMIT_REF_NAME url: http://$CI_COMMIT_REF_SLUG.docker.eduard.win/index.html tags: - test script: - docker run -d --expose 52773 --volume /InterSystems/durable/$CI_COMMIT_REF_SLUG:/data --env ISC_DATA_DIRECTORY=/data/sys --env VIRTUAL_HOST=$CI_COMMIT_REF_SLUG.docker.eduard.win --name iris-$CI_COMMIT_REF_NAME docker.eduard.win/test/docker:$CI_COMMIT_REF_NAME --log $ISC_PACKAGE_INSTALLDIR/mgr/messages.log
Nginx容器使用VIRTUAL_HOST
环境VIRTUAL_HOST
自动将请求重定向到指定的端口-在这种情况下为52773。
由于必须在InterSystems IRIS的主机上存储一些数据(密码,%SYS,应用程序数据),因此存在Durable%SYS功能,可以将数据存储在主机上,例如:
iris.cpf
是主要配置文件。/csp
包含Web应用程序文件。/httpd/httpd.conf
和私有Apache服务器配置。/mgr
目录存储在其中:
- 数据库
IRISSYS
, IRISTEMP
, IRISAUDIT
, IRIS
, USER
。 IRIS.WIJ
。- 目录
/journal
存储杂志。 /temp
目录中的临时文件。- 记录
messages.log
, journal.log
, SystemMonitor.log
。
要启用Durable%SYS,请指定ISC_DATA_DIRECTORY
主机目录的volume
参数,并且ISC_DATA_DIRECTORY
变量设置用于存储Durable%SYS文件的目录。 该目录不应该存在,它将自动创建。
因此,我们的容器化应用程序的体系结构如下:

要构建这样的应用程序,我们至少必须创建一个其他数据库(以保存应用程序代码)并在应用程序区域中创建其映射。 我使用USER
范围存储应用程序数据,因为默认情况下将此范围添加到Durable%SYS。 应用程序代码存储在容器中,以便可以对其进行更新。
基于上述内容, %安装程序应:
- 创建
APP
区/数据库 - 将代码上传到
APP
区域 - 在
USER
区域中创建我们的应用程序的映射类 - 执行其他配置(我创建了CSP Web应用程序和REST Web应用程序)
代码%安装程序 Class MyApp.Hooks.Local { Parameter Namespace = "APP"; /// See generated code in zsetup+1^MyApp.Hooks.Local.1 XData Install [ XMLNamespace = INSTALLER ] { <Manifest> <Log Text="Creating namespace ${Namespace}" Level="0"/> <Namespace Name="${Namespace}" Create="yes" Code="${Namespace}" Ensemble="" Data="IRISTEMP"> <Configuration> <Database Name="${Namespace}" Dir="/usr/irissys/mgr/${Namespace}" Create="yes" MountRequired="true" Resource="%DB_${Namespace}" PublicPermissions="RW" MountAtStartup="true"/> </Configuration> <Import File="${Dir}Form" Recurse="1" Flags="cdk" IgnoreErrors="1" /> </Namespace> <Log Text="End Creating namespace ${Namespace}" Level="0"/> <Log Text="Mapping to USER" Level="0"/> <Namespace Name="USER" Create="no" Code="USER" Data="USER" Ensemble="0"> <Configuration> <Log Text="Mapping Form package to USER namespace" Level="0"/> <ClassMapping From="${Namespace}" Package="Form"/> <RoutineMapping From="${Namespace}" Routines="Form" /> </Configuration> <CSPApplication Url="/" Directory="${Dir}client" AuthenticationMethods="64" IsNamespaceDefault="false" Grant="%ALL" Recurse="1" /> </Namespace> </Manifest> } /// This is a method generator whose code is generated by XGL. /// Main setup method /// set vars("Namespace")="TEMP3" /// do ##class(MyApp.Hooks.Global).setup(.vars) ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 0, pInstaller As %Installer.Installer) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "Install") } /// Entry point ClassMethod onAfter() As %Status { try { write "START INSTALLER",! set vars("Namespace") = ..#Namespace set vars("Dir") = ..getDir() set sc = ..setup(.vars) write !,$System.Status.GetErrorText(sc),! set sc = ..createWebApp() } catch ex { set sc = ex.AsStatus() write !,$System.Status.GetErrorText(sc),! } quit sc } /// Modify web app REST ClassMethod createWebApp(appName As %String = "/forms") As %Status { set:$e(appName)'="/" appName = "/" _ appName #dim sc As %Status = $$$OK new $namespace set $namespace = "%SYS" if '##class(Security.Applications).Exists(appName) { set props("AutheEnabled") = $$$AutheUnauthenticated set props("NameSpace") = "USER" set props("IsNameSpaceDefault") = $$$NO set props("DispatchClass") = "Form.REST.Main" set props("MatchRoles")=":" _ $$$AllRoleName set sc =
我注意到要创建不在主机上的数据库,我使用目录/usr/irissys/mgr
,因为调用##class(%File).ManagerDirectory()
返回Durable%SYS的目录路径。
测验
现在运行测试。
test image: stage: test tags: - test script: - docker exec iris-$CI_COMMIT_REF_NAME irissession iris -U USER "##class(isc.git.GitLab).test()"
派送
测试通过后,我们将在Docker Registry中发布映像。
publish image: stage: publish tags: - test script: - docker login docker.domain.com -u user -p pass - docker push docker.domain.com/test/docker:$CI_COMMIT_REF_NAME
可以使用秘密变量传递登录名/密码。
现在,图像显示在GitLab上。

其他开发人员可以从Docker Registry下载它。 在“环境”选项卡上,可以查看我们所有的环境:
结论
本系列文章讨论了持续集成的常用方法。 在InterSystems平台上自动化应用程序的组装,测试和交付是可能的,并且易于实现。
容器化技术的使用将有助于优化应用程序开发和部署过程。 消除环境之间的不一致使测试和调试更加容易。 编排允许您创建可伸缩的应用程序。
参考文献