0 关注者 · 14 帖子

Git 是一个版本控制系统,用于跟踪计算机文件的变更,并在多人之间协调文件处理工作。

官方网站

文章 Nicky Zhu · 十月 31, 2025 20m read

目录

  1. 本文目的
  2. 什么是容器,它们为什么对 IRIS 有意义
     2.1 容器和镜像简介
     2.2 为什么容器对开发者很有用
     2.3 为什么 IRIS 可以很好地与 Docker 配合使用
  3. 先决条件
  4. 安装 InterSystems IRIS 镜像
     4.1 使用 Docker Hub
     4.2 拉取镜像
  5. 运行 InterSystems IRIS 镜像
     5.1 启动 IRIS 容器
     5.2 检查容器状态
     5.3 在容器终端执行代码
     5.4 访问 IRIS 管理门户
     5.5 将容器连接到 VS Code
     5.6 停止或移除容器
     5.7 使用绑定挂载设置特定密码
     5.8 使用持久化 %SYS 卷
      5.8.1 可以使用持久化 %SYS 存储什么
      5.8.2 如何启用持久化 %SYS
  6. 使用 Docker Compose
     6.1 Docker Compose 示例
     6.2 运行 Docker Compose
  7. 使用 Dockerfile 运行自定义源代码
     7.1 Dockerfile 示例
     7.2 Docker Compose 示例
     7.3 了解层、镜像标记和构建与 运行时
     7.4 源代码和初始化脚本
     7.5 使用 Dockerfile 构建镜像
     7.6 在容器化 IRIS 终端中运行指令
  8. 结语和未来计划


1. 本文目的

InterSystems 开发者社区已经有很多优秀的文章解释了 Docker 是什么、最重要的命令以及 InterSystems IRIS 在容器化环境中的多个用例。

本系列文章的目的略有不同。 由于我非常喜欢分步指南,我想创建一份全面的讲解,介绍如何在 InterSystems IRIS 中配置和使用 Docker,我们先从最基础的场景开始,逐步过渡到更高级的场景,例如多命名空间实例、互连容器、与外部系统的集成以及用于了解 UI 的应用程序。

2. 什么是容器,它们为什么对 IRIS 有意义

2.1 容器和镜像简介

按照传统方法,要想运行应用程序,需要将版本与您的操作系统进行匹配,并针对特定目标对其进行打包。 同时,每个应用程序都应当打包,以便专门与某个目标系统配合使用。 如果您希望应用程序在 macOS 和 Windows 上运行,必须更改应用程序设计并针对不同的系统进行打包。Docker 镜像和容器是应用程序部署技术,通过让开发者将软件打包一次并在任何地方运行来解决此问题。

Docker 是一个将软件打包到容器中的软件平台。 Docker 镜像(或容器镜像)是独立的可执行文件,包含创建和运行容器的所有指令(库、依赖项和文件)。 Docker 镜像可共享、可移植,因此您可以一次在多个位置部署同一个镜像。Docker 容器是一个运行时环境,具有运行应用程序代码所需的所有必要组件,无需使用主机依赖项。  

与虚拟机不同,容器是轻量级的。 它们不需要完整的操作系统,只使用特定应用程序所需的二进制文件和库,通过 Docker 引擎直接在主机操作系统上运行,这使其更加高效。 多个隔离的容器可以在同一台计算机上并行启动,互不干扰。

2.2 为什么容器对开发者很有用

  • 隔离:在清晰、可重现的环境中运行,不会影响您的主机系统。
  • 可重现性:确保您的设置在不同的计算机上以相同的方式工作。
  • 轻松设置:使用一个命令,只需几秒钟即可启动 IRIS 实例,无需手动安装。

2.3 为什么 IRIS 可以很好地与 Docker 配合使用

在 Docker 中运行 InterSystems IRIS 有几项优势:

  • 应用程序运行并且可以在隔离的容器中进行测试
  • 可以使用共享版本控制系统(如 Git),无需直接在服务器上操作
  • 容器化环境可以在任何阶段重现,从而在整个软件生命周期中保持一致性。
  • 您的应用程序可以轻松地在每台计算机上运行。

3. 先决条件

要在 Docker 容器中运行 InterSystems IRIS,您必须具有:

4. 安装 InterSystems IRIS 镜像

4.1 使用 Docker Hub

Docker Hub 是 Docker 镜像的中心注册表。 它提供了一个庞大的预构建镜像库,您可以将其作为切入点。 InterSystems 在其中发布官方 IRIS 社区版镜像,您可以下载该镜像以在自己的容器中本地运行 IRIS。 您还可以使用 Docker Hub 推送自己的自定义镜像,以便在团队中共享或分发给社区。Docker Hub 既可以在线使用,也可以嵌入到 Docker Desktop 中。

4.2 拉取镜像

一些常见的 Docker 镜像命令包括:

命令描述
docker pull <image>从 Docker Hub 下载镜像
docker images列出所有本地镜像
docker rmi <image>移除一个或多个镜像

您可以直接在镜像的 Docker Hub 页面上找到确切的拉取命令:

对于 InterSystems IRIS 镜像,该命令为:

docker pull intersystems/iris-community:latest-cd

或者,您可以在 Docker Desktop 搜索栏中搜索 iris-community 并点击 Pull

安装后,您应当拥有在本地镜像中列出的 InterSystems IRIS 镜像:

5. 运行 InterSystems IRIS 镜像

从 Docker Hub 中拉取出 InterSystems IRIS 镜像后,您可以在容器内运行该镜像。

以下是常用的 Docker 容器命令

命令描述
docker run -d <image>以分离模式运行:在后台启动容器并立即将控制权返还给终端。
docker run -p <host>:<container> <img>将主机端口映射到容器端口
docker ps列出正在运行的容器
docker ps -a列出所有容器(包括已停止的容器)
docker exec -it <container> bash在正在运行的容器中执行命令
docker logs <container>查看容器日志
docker stop <container>停止正在运行的容器
docker start <container>启动已停止的容器
docker restart <container>重启容器
docker rm <container>移除容器

5.1 启动 IRIS 容器

您可以通过 Docker Desktop UI 启动名为“my-iris”的 InterSystems IRIS 社区版容器,只需在 Images 面板中点击要运行的镜像的 Run 按钮即可。 对于 InterSystems 镜像,可以指定一些可选的设置,例如在主机上公开哪些端口来与容器内运行的服务进行通信。

可以通过上面显示的菜单完成操作,具体如下:

  • 左侧 → 主机上的端口
  • 右侧 → 容器内的端口

常用 IRIS 端口(容器内)

  • 1972 → 超级服务器端口:由 IRIS 用于网络协议(ObjectScript、JDBC 等)。
  • 52773 → Web 服务器端口:由 IRIS 用于管理门户(Web 界面)。

如果没有显式映射端口,Docker 将在主机上指定随机端口。

或者,可以使用终端来运行容器:

docker run --name my-iris -d --publish 9091:1972 --publish 9092:52773 intersystems/iris-community:latest-cd

在本例中:

  • 主机上的 9091 映射到容器内的 1972(超级服务器)。
  • 主机上的 9092 映射到容器内的 52773(管理门户)。

5.2 检查容器状态

运行此命令后,运行 docker ps 以验证容器是否正常运行。

> docker ps
CONTAINER ID   IMAGE                                   COMMAND                 CREATED         STATUS                            PORTS                                                                                        NAMES
907d4c2b4ab5   intersystems/iris-community:latest-cd   "/tini -- /iris-main"   3 seconds ago   Up 2 seconds (health: starting)   0.0.0.0:9091->1972/tcp, [::]:9091->1972/tcp, 0.0.0.0:9092->52773/tcp, [::]:9092->52773/tcp   my-iris

正在运行的容器也会列在 Docker Desktop Containers 面板中:

相关镜像的状态将是 In Use,如 Images 面板中所示:

5.3 在容器终端执行代码

当容器状态显示 Running 时,表示一切正常。 

您可以进行测试,只需打开容器终端(点击 Docker Desktop 的 Containers 面板内的容器名称并转到 Exec)并输入以下内容即可:

iris session IRIS

这将在 Docker 容器中打开一个 IRIS 终端,您可以在其中使用标准的 ObjectScript 语法来执行命令和脚本。

5.4 访问容器化实例的 IRIS 管理门户

接下来,打开浏览器并导航到:

http://localhost:9092/csp/sys/UtilHome.csp

默认情况下,IRIS 容器使用用户 _SYSTEM 和密码 SYS。登录后需要更改密码。

这将为您提供 IRIS 管理门户的完整访问权限,这样您可以直接从 Web 界面管理命名空间、数据库和其他 IRIS 功能。

5.5 将容器连接到 VSCode IDE

您可以使用映射的超级服务器端口(默认为容器内的 1972,例如主机上的 9091)和 Web 服务器端口(默认为容器内的 52773,例如主机上的 9092)将您喜欢的 IDE(例如 VS Code 或 Studio)连接到 IRIS 容器。 这样您可以直接针对正在运行的容器开发和测试 ObjectScript 代码。

将容器连接到 VSCode:

  • 安装 InterSystems ObjectScript 扩展程序包
  • 打开 InterSystems Server 扩展程序
  • 点击三个点和“Edit server”
  •  
  • 点击“Edit in settings.json”
  • 将此元素添加到“intersystems.servers”json:
"docker_iris": {
    "webServer": {
        "scheme": "http",
        "host": "localhost",
        "port": 9092
    },
    "description": "Connection to Docker container."
}
  • 服务器现已连接。 您可以使用 _SYSTEM 用户登录。
  • 您可以看到,USER 是唯一可用的命名空间:

5.6 停止或移除容器

要停止正在运行的容器,请使用以下命令:

docker stop my-iris

再次启动容器

要再次启动容器,请使用以下命令:

docker start my-iris

移除(删除)容器

要移除容器实例,但移除镜像,请使用:

docker rm my-iris

除非您挂载了卷,否则容器内的所有数据都将丢失(我们将在后续段落中讨论这个问题)。

5.7 使用绑定挂载设置特定密码

在启动容器时,可以使用密码文件设置自定义密码。 该文件将保存在我们的主机上,并使用绑定挂载机制复制到容器中。

当您使用绑定挂载时,主机上的文件或目录将从主机挂载到容器中,从而允许在 Docker 主机上的开发环境和容器之间共享源代码或构建工件。

  • 创建名为 password.txt 的文件,其中仅包含字符串形式的密码(注意! 记下密码,以后会用到)。
  • 复制其路径,本例中为 C:\InterSystems\DockerTest\password\password.txt
  • 运行以下命令:
docker run --name my-iris -d --publish 9091:1972 --publish 9092:52773 --volume "C:\InterSystems\DockerTest:/durable" intersystems/iris-community:latest-cd --password-file /durable/password/password.txt 

容器运行后,可以使用用户 _SYSTEM  和您在密码文件中写下的密码登录。

在 Docker 容器内,您会看到一个 password.txt.done 文件。您的主机文件夹中会有一个相同的文件。

文件扩展名会更改为 .done,以避免密码以纯文本形式保留。 这是 InterSystems IRIS 中对密码文件的标准操作。 因此,在从 password.txt 文件读取密码并将默认 IRIS 用户 (_SYSTEM) 更新为该密码后,出于安全原因,该文件会附加 .done 并移除密码。

您可以使用自定义密码登录管理门户(我告诉过您记下来 :D)。 登录后,InterSystems IRIS 不会强迫您更改密码。

请注意,如果在没有挂载任何持久化卷的情况下移除了容器,然后重启(有关详情,请参阅下一段),则不会再次从 password.txt 文件中读取密码,因为它已被替换为 password.txt.done。 如果是这种情况,将使用标准的“SYS”密码。 

5.8 使用特定的持久化卷启动容器

默认情况下,当您使用 docker rm <container's name> 命令移除正在运行的 Docker 容器时,保存在该容器内的任何内容都将消失。

为了避免丢失数据,InterSystems IRIS 提供了持久化 %SYS 功能。 这可以让实例将所有重要的文件都存储在您的主机上,以便在容器和实例重启后它们仍然存在。

5.8.1 可以使用持久化 %SYS 存储什么?

一些示例包括:

  • 配置文件(iris.cpfhttpd.conf
  • Web 网关配置和日志 (/csp)
  • 系统数据库(IRISUSERIRISSECURITYIRISTEMP 等)
  • 日志、写入镜像文件 (WIJ) 和临时文件
  • 日志文件(messages.logSystemMonitor.log 等)
  • 许可证密钥 (iris.key)
  • 您创建的任何其他数据库

5.8.2 如何启用持久化 %SYS

首先,从主机中选择一个文件夹,如 C:\InterSystems\DockerTest

  • 在 Linux 上,创建 irisowner 用户并为其授予目录所有权,确保 IRIS 可以写入该文件夹:
adduser irisowner
chown -R irisowner:irisowner /InterSystems/DockerTest
  • 在 Windows 上,您可以跳过这一段,因为 Docker Desktop 将使用您当前的 Windows 用户权限挂载该文件夹。

然后,挂载您的主机文件夹并使用 ISC_DATA_DIRECTORY 变量告知 IRIS 将其持久化数据写入何处:

  --volume "C:\InterSystems\DockerTest:/durable" --env ISC_DATA_DIRECTORY=/durable/iris

在终端上运行的完整指令是:

docker run --name my-iris -d --publish 9091:1972 --publish 9092:52773 --volume "C:\InterSystems\DockerTest:/durable" --env ISC_DATA_DIRECTORY=/durable/iris intersystems/iris-community:latest-cd --password-file /durable/password/password.txt

此时,您可以检查您的容器,看到一个持久化文件夹已装载并包含目录 iris/(对于持久化 %SYS)和 password/。 在主机上,您也可以看到以下两个目录:

如果容器停止并被移除,当您使用相同的 Docker Compose 命令重新创建容器时,IRIS 将使用 iris/ 文件夹中的数据恢复其先前的状态(用户、配置、日志、数据库等),因此不会丢失任何内容。 您可以通过创建 Web 应用程序、停止并移除容器,然后再次创建来进行测试。 如果不使用持久化 %SYS 功能,每次移除容器并启动新实例时,%SYS 内的任何更改都会丢失。

请注意,如果删除了 iris/ 文件夹,下次启动 IRIS 容器时,由于无法找到先前的 %SYS 数据,它将像在全新安装中一样进行初始化。 将创建一个全新的 iris/ 文件夹。

6. 使用 Docker Compose 

到目前为止,您一直在使用一个长命令 docker run 启动 InterSystems IRIS。 这虽然可行,但很快就会难以使用普通的 Shell 命令来管理一切。

Docker Compose 是一个 YAML 配置文件,用于定义如何运行一个或多个容器。这简化了整个应用程序栈的控制,使管理服务、网络和卷变得更加容易。 只需一个命令,即可从配置文件创建并启动所有服务。

以下是 Docker compose 的常用命令

命令描述
docker compose up -d以分离模式启动 docker-compose.yml 中定义的所有服务(默认情况下,使用当前文件夹中的 docker-compose.yml 文件)。
docker compose -f ./path/to/docker-compose.yml up -d从另一个目录中的 compose 文件启动服务。
docker compose down停止并移除由 docker compose up 创建的所有容器、网络和卷。
docker compose ps列出由 Compose 管理的容器。
docker compose logs查看 Compose 文件中定义的所有服务的日志。
docker compose logs <service>查看特定服务(例如,iris)的日志。
docker compose exec <service> bash在由 Compose 管理的正在运行的容器内打开一个 Shell。
docker compose stop停止正在运行的容器,但不将其移除。
docker compose start启动之前停止的容器。
docker compose restart重启 Compose 文件中定义的所有容器。
docker compose build构建或重建由 Dockerfile 定义的服务。
docker compose pull拉取服务的最新镜像。
docker compose config验证并查看 Compose 文件中的合并配置。

6.1 Docker Compose 示例

要使用 Docker Compose,必须创建一个 docker-compose.yml 文件,其中包含您要创建和启动的容器的所有配置。

这样一来,以下命令:

docker run --name my-iris -d --publish 9091:1972 --publish 9092:52773 --volume "C:\InterSystems\DockerTest:/durable" --env ISC_DATA_DIRECTORY=/durable/iris intersystems/iris-community:latest-cd --password-file /durable/password/password.txt

可被替换为下方的 docker-compose.yml

# docker-compose.yml     
services:
  iris:
    container_name: my-iris
    image: intersystems/iris-community:latest-cd
    init: true
    volumes:
      # System/persistent data (IRIS installation, databases, etc.)# On Windows, you can use either: "C:\\InterSystems\\DockerTest:/durable"
      - C:/InterSystems/DockerTest:/durable
    ports:
      - "9092:52773"# Management Portal / REST APIs
      - "9091:1972"# SuperServer port
    environment:
      - ISC_DATA_DIRECTORY=/durable/iris
    # Use the password file to log within the container
    command: --password-file /durable/password/password.txt

6.2 运行 Docker Compose 

在保存 Docker Compose 文件的目录中打开一个终端(或使用 -f 选项)并运行以下命令:

docker compose up -d

您的 IRIS 容器将以 Docker Compose 文件中指定的确切配置启动。

在 Docker Desktop 中,您现在可以看到一个名为“dockertest”的 Compose 栈(它采用保存 Docker Compose 的文件夹的名称)已经创建并与容器“my-iris”相关联:

7. 使用 Dockerfile 运行自定义源代码

到目前为止,您一直在直接从官方 Docker 镜像运行 InterSystems IRIS。 不过,在构建镜像时,我们可能需要自动加载镜像中的 ObjectScript 类或其他自定义代码。

Dockerfile是一个文本文件,其中包含从基础镜像(如 intersystems/iris-community:latest-cd)开始构建镜像的指令。 使用 Dockerfile,我们可以将自定义源代码添加到容器中,并在构建镜像时运行自定义命令。

7.1 Dockerfile 示例

下一个示例提供了一个执行以下操作的 Dockerfile:

  • 从官方 InterSystems IRIS 镜像启动镜像。
  • 将应用程序代码从 src/ 文件夹复制到容器。
  • 运行脚本来导入类并初始化容器化的应用程序,然后将日志保存到日志文件中。
  • 公开默认的 InterSystems IRIS 端口。

7.2 Docker Compose 示例

您还应当修改 Docker Compose,以指定从当前文件夹中的 Dockerfile 构建镜像:

# docker-compose.yml     
services:
  iris:
    container_name: my-iris
    build: # this tells Docker to build a new image based on the one specified in the Dockerfile
      context: .        # build the image from local Dockerfile
      dockerfile: Dockerfile
    image: my-modified-iris-image:latest   # give the new image a new tag to avoid overriding the base image
    init: true
    volumes:
      # System/persistent data (IRIS installation, databases, etc.)# On Windows, you can use either: "C:\\InterSystems\\DockerTest:/durable"
      - C:/InterSystems/DockerTest:/durable
    ports:
      - "9092:52773"# Management Portal / REST APIs
      - "9091:1972"# SuperServer port
    environment:
      - ISC_DATA_DIRECTORY=/durable/iris
    # Use the password file to log within the container
    命令:--password-file /durable/password/password.txt

7.3 了解层、镜像标记和构建与 运行时

在 Docker 中,正如什么是镜像?中所述,有两个重要的原则需要牢记:

  1. 镜像不可变:创建完镜像后,将无法进行修改。 您只能生成新的镜像或在镜像上添加更改。
  2. 容器镜像由层组成:每一层代表一组添加、移除或修改文件的文件系统更改。

执行 Dockerfile 中指定的指令时,新层会添加到镜像中。 这些层也将显示在镜像信息中。

考虑到这一点,务必区分 Docker 在构建时(当它从 Dockerfile 创建镜像时)和运行时(当它从该镜像启动容器时)的作用。

以下每个 Dockerfile 指令都在构建时执行:

  • COPY:将文件复制到镜像中
  • RUN:执行命令并将结果保存为新的镜像层
  • ENTRYPOINT:不改变文件系统,而是定义将在运行时启动的默认进程
  • EXPOSE:设置元数据,指示镜像打算使用哪些端口

运行时,Docker 不会重建镜像,而是在镜像上添加一个容器层。容器运行时所做的所有更改(如新文件、编辑、日志)都会进入这个临时层。

从上一个 Docker Compose 示例来看:

build: # this tells Docker to build a new image based on the one specified in the Dockerfile
  context: .        # build the image from local Dockerfile
  dockerfile: Dockerfile
image: my-modified-iris-image:latest   # give the new image a new tag to avoid overriding the base image

这些指令通过拉取基础镜像并按照 Dockerfile 中的描述进行修改,告知 Docker 创建一个名为“my-modified-iris-image:latest”的新镜像(这称为标签)。 用其他名称标记新镜像非常重要。如果我们不在新创建的镜像上放置标签,基础镜像会被新镜像覆盖。 官方镜像仍在 Docker Hub 上提供,但此本地版本会对其进行映射,并且每个引用该标签的项目现在都会不知不觉地使用包含多个新层的自定义镜像。

为了避免这种情况,请务必使用不同的标签来创建新的单独镜像,同时确保官方基础镜像干净、可重用。

7.4 源代码和初始化脚本

此时,我们应至少创建一个类,并将其放在 src/ 文件夹中。 该类将被复制到容器中,并通过 iris.script 文件导入,该文件包含在构建镜像时导入类和初始化应用程序所需的所有指令。

在项目文件夹中创建一个名为 src/DockerStepByStep 的新目录并创建以下类文件:

Class DockerStepByStep.cheers Extends%RegisteredObject
{

ClassMethod sayHi() As%Status { Set sc = $$$OKw"Hi mom!",! Return sc }

}

在项目的根目录中,创建一个名为 iris.script 的文件:

// Unexpire passwords for dev modezn"%SYS"Do##class(Security.Users).UnExpireUserPasswords("*")

// Load classes from durable sourcezn"USER"// Import classesset importPath = "/opt/irisapp/src"write"Loading classes at '", importPath, "' ...", ! set errors = ""do$System.OBJ.Import(importPath,"cuk",,.errors) if errors = 0 { write"Classes loaded successfully", ! } else { write errors, " errors occurred while loading classes!", ! }

halt

7.5 使用 Dockerfile 构建镜像

首次运行时,您可以使用以下命令从 Dockerfile 构建镜像(--build 标记会强制 Docker 从您的 Dockerfile 重建镜像):

docker-compose up --build

对于其他运行,如果 Dockerfile 未被修改,则可以简单地运行:

docker-compose up -d

开始构建镜像后,您会在终端中看到以下日志:

PS C:\InterSystems\DockerTest> docker-compose up --build
[+] Building 21.5s (13/13) FINISHED
 => [internal] load local bake definitions                                                                     0.0s
 => => reading from stdin 530B                                                                                 0.0s
 => [internal] load build definition from Dockerfile                                                           0.0s
 => => transferring dockerfile: 1.73kB                                                                         0.0s
 => [internal] load metadata for docker.io/intersystems/iris-community:latest-cd                              10.0s
 => [internal] load .dockerignore                                                                              0.0s
 => => transferring context: 2B                                                                                0.0s 
 => [1/6] FROM docker.io/intersystems/iris-community:latest-cd@sha256:93488df381f5868649e7bfc33a9083a3e86a22d  0.9s 
 => => resolve docker.io/intersystems/iris-community:latest-cd@sha256:93488df381f5868649e7bfc33a9083a3e86a22d  0.0s 
 => [internal] load build context                                                                              0.0s 
 => => transferring context: 147B                                                                              0.0s
 => [2/6] WORKDIR /opt/irisapp                                                                                 0.0s
 => [3/6] COPY src src                                                                                         0.1s 
 => [4/6] COPY iris.script .                                                                                   0.1s
 => [5/6] RUN mkdir -p /opt/irisapp/logs                                                                       0.3s
 => [6/6] RUN iris start IRIS &&     iris session IRIS < iris.script > /opt/irisapp/logs/build.log 2>&1 &&     4.5s 
 => exporting to image                                                                                         4.5s 
 => => exporting layers                                                                                        3.3s 
 => => exporting manifest sha256:3ce316cefa21a3707251c4287005a15b02e6dc0151b24baf2a82f76064792250              0.0s 
 => => exporting config sha256:00238e19edef86b29149d2eb89ff75f4d1465ba0d9a2ac4494a14d3bd3746a94                0.0s 
 => => exporting attestation manifest sha256:3579cab5c8accc7958090276deb60bd7dbbc2ecbf13af8e7fa8c4ff2dfe91028  0.0s 
 => => exporting manifest list sha256:17b969c340f57d611cc7603287cc6db50cffd696258a72b5648ece0a919676ac         0.0s 
 => => naming to docker.io/intersystems/iris-community:latest-cd                                               0.0s 
 => => unpacking to docker.io/intersystems/iris-community:latest-cd                                            0.9s 
 => resolving provenance for metadata file                                                                     0.0s 
[+] Running 3/3
 ✔ intersystems/iris-community:latest-cd  Built                                                                0.0s 
 ✔ Network dockertest_default             Created                                                              0.1s 
 ✔ Container my-iris                      Created                                                              0.2s 

在第 6/6 步中,iris.script 文件被执行到容器化的 IRIS 实例中,日志保存在路径 /opt/irisapp/logs/build.log 中。

要查看日志,可以运行以下指令:

docker exec -it my-iris cat /opt/irisapp/logs/build.log

您应当看到以下记录,这些记录会告知您类编译的结果:

Node: buildkitsandbox, Instance: IRIS

USER>

USER>

%SYS>

%SYS>

%SYS>

%SYS>

USER>

USER>

USER>

USER> Loading classes at '/opt/irisapp/src' ...

USER>

USER>

Load of directory started on 09/16/202507:46:28 Loading file /opt/irisapp/src/DockerStepByStep/cheers.cls as udl

Compilation started on 09/16/202507:46:28 with qualifiers 'cuk' Class DockerStepByStep.cheers is up-to-date. 编译在 0.005s 内成功完成。

Load finished successfully.

USER> Classes loaded successfully

USER>

USER>

在 Docker Desktop 上,您可以看到一个名为“my-modified-iris-image”的新镜像已创建,并且正在与基础官方镜像一起运行。

如果检查这些镜像,您会发现自定义镜像由 31 层组成,而不是原始镜像的 25 层。 新层与 Dockerfile 在构建时执行的指令相对应:

7.6 在容器化 IRIS 终端中运行指令

要测试这些类,您可以在容器化 IRIS 实例中激活 IRIS 终端。 为此,请执行以下指令:

docker exec -it my-iris iris session IRIS

此时,您可以使用以下命令从 USER 命名空间调用 ClassMethod:

do##class(DockerStepByStep.cheers).sayHi()

最后,您应当看到以下输出:

PS C:\InterSystems\DockerTest> docker exec -it my-iris iris session IRIS

Node: 41c3c7a9f2e4, Instance: IRIS

USER>do##class(DockerStepByStep.cheers).sayHi() Hi mom!

8. 结语和未来计划

我们已经完成了在 Docker 中运行 InterSystems IRIS 的整个周期:

  • 拉取镜像
  • 启动并配置容器
  • 使用持久化 %SYS 来持久化数据
  • 使用自己的代码和脚本构建自定义镜像

以上内容足以让您在容器化环境中试验 IRIS 并将其用于开发。

您可以访问 GitHub 仓库,其中包含最后一部分讨论的所有文件(Docker Compose、Dockerfile、iris.script 等)。

敬请期待下一篇文章!

1
0 15
文章 Lilian Huang · 十一月 6, 2025 6m read

我很清楚对于那些完全不熟悉 VS Code、Git、Docker、FHIR 和其他工具的人来说,设置环境时会遇到一些困难。 所以我决定写这篇文章,详细介绍整个设置过程,以便大家能够轻松上手。

如果您能在本文最后留下评论,告诉我说明是否清楚,是否有遗漏,或者是否有其他您觉得有用的东西,我将不胜感激。

设置包括:

✅ VS Code – 代码编辑器
✅ Git – 版本控制系统
✅ Docker – 运行 IRIS for Health Community 的实例
✅ VS Code REST 客户端扩展程序 – 用于运行 FHIR API 查询
✅ Python – 用于编写基于 FHIR 的脚本
✅ Jupyter Notebook – 用于 AI 和 FHIR 任务

准备工作:确保您在系统上拥有管理员权限

除了阅读本指南,您还可以按照视频中的步骤操作:

如果您是 Windows 系统(请注意:原文是YouTube视频,请跳转至EN原帖查看)

0
0 13
InterSystems 官方 Michael Lei · 一月 7, 2025

我们已经有一段时间没有在开发者社区上发表关于嵌入式 Git 的文章了,我想借此机会更新一下今年我们完成的大量工作以及未来的工作计划。

背景信息

如果您要在 IRIS 上构建解决方案,并想要使用 Git,那就太棒了! 只需将 VSCode 与本地 Git 仓库结合使用,并将更改推送到服务器上即可,就是这么简单。

但在以下使用场景中该怎么办:

  • 您与其他开发者在共享的远程开发环境中合作,并想要避免因同时编辑同一文件而影响彼此的工作
  • 您使用位于管理门户中的编辑器来实现互操作性或业务智能,并希望对您的工作直接进行源代码控制,即使在本地容器中也希望如此
  • 您仍在使用 Studio 处理一些任务并且/或偶尔会再次从 VSCode 跳转回 Studio;或者您的团队还没有完全接受 VSCode,一些团队成员仍想使用 Studio,而其他人在使用 VSCode
  • 您同时在同一命名空间中处理多个独立的项目,例如使用 InterSystems Package Manager 定义的多个软件包,并想仅通过一个 isfs 编辑视图使用所有软件包(而不是在多个单独的项目中使用),同时在合适的本地 git 仓库中自动跟踪更改

在以上任何情况下,您都非常需要嵌入式源代码控制。 您可能听说过“服务器端源代码控制”或“源代码控制挂钩”这类叫法,它们的含义相同,都表示可在所有编辑器(包括 IDE 和图形编辑器)中对 IRIS 实例实现一致的源代码控制行为。 所有控制操作都是针对与您的 IRIS 实例位于同一位置的源代码控制仓库执行的,该仓库可能位于远程服务器上、您自己的机器上,甚至可能位于容器中。

如果您希望开始使用 Git 进行嵌入式源代码控制,可以先从嵌入式 Git (https://github.com/intersystems/git-source-control) 着手。 嵌入式 Git 不是 InterSystems 支持的产品,但得到了 InterSystems 内我的团队(应用程序服务团队)和广大用户社区的大力支持。 我们始终欢迎大家提出 PR 和 GitHub 议题,我们会监控开发者社区活动(特别是使用相对较新的“嵌入式 Git”标签)。

2024 年嵌入式 Git 工作回顾

2024 年初,我曾向已能轻松使用 Git 并愿意在必要时进行深入研究、更偏向技术型的用户推荐嵌入式 Git。 现在,我极力推荐所有人使用嵌入式 Git。 为了展示我们对此工具倾注的心血以及我们在 2024 年取得的飞跃,下方是过去几年的提交量图:

如果您想了解我们的最新动态,可以查看版本或我们的变更日志,但下面是对要点的总结:

  • 7 月,我们添加了“基本模式”(具体说明请参阅此处),其中的“同步”操作可以简化拉取/提交/变基/推送工作流,我们推荐大家在一般情况下使用该模式,尤其是那些不太熟悉 Git 的用户。
  • 在多个版本中,我们将 git 拉取和签出操作(实际上是任何修改 Git 仓库状态的操作)无缝同步回 IRIS,从而无需强制重新加载整个代码库便可确保所有内容均为最新状态。
  • 在我们的 9 月版本中,通过从管理门户下载 VSCode 工作空间文件,您可以非常轻松地建立 isfs 连接。
  • 在我们的 11 月版本中,可以通过扩展设置页面进行更多配置,无需在终端运行命令,并针对互操作性设置中的两种最常见(几乎一定会出现)的用例增加了智能合并冲突解决功能。
  • 除了以上工作之外,我们还解决了数十个小错误和实用性问题,简化了管理门户的导航,并增加了很多小功能,以简化互操作性用例以及跨多个开发者特定命名空间在共享远程实例上的协作(我们认为这是一种常用方式,并推荐大家使用)。

即将推出

下一个版本 (2.8.0) 预计在两周内发布,其中将包含我们历经数月开发出的“生产分解”功能。 互操作性生产通常以单个文件的形式进行源代码控制,由于一次只能由一人编辑生产,这会导致并发问题。 共享开发环境中还存在一些合并问题,这些问题几乎一定会出现,虽然我们可以通过智能化方式解决它们,但在某些分支工作流中,这些问题很可能会继续出现。 利用生产分解功能,每个业务主机(服务、进程或操作)在源代码控制中都会以单个文件的形式表示。 如果您有兴趣尝试此功能,请联系我们!

进入 2025 年,我们将继续保持嵌入式 Git 的发展势头,重点关注其他身份验证方法并支持其他常见部署模式。 您可以访问 2025 H1 里程碑了解我们的大致计划(其中包括几次版本发布;我们通常会每月发布次要版本,并根据需要发布补丁版本修复重要错误)。

如果您有兴趣深入了解开发细节/获取最新开发信息,欢迎加入我们每周五召开的利益相关者会议。 此会议也成为了嵌入式 Git 的一种“办公时间”。 请随时留言告诉我您的电子邮件地址,我会邀请您加入会议。

0
0 95
文章 Nicky Zhu · 十月 10, 2024 7m read

本演示程序用于展示如何采用自定义FHIR profile来验证数据合规性。自定义FHIR实施指南基于FHIR R4版本开发,在本例中实现了对Organization资源的扩展并用于验证数据的合规性。

安装

  1. 通过Git clone下载本项目。
  2. 执行docker-compose up -d构建并启动容器,初次执行时需执行需10~15分钟(视配置变化)。将构建InterSystems IRIS for Health镜像,安装FHIR服务器,导入自定义FHIR规范,使自定义FHIR 规范可用于验证数据。
  3. 在Postman中导入TestCases中的测试用例文件,查看各类FHIR约束的测试效果
  4. 容器启动后可查看自定义IG内容

项目代码结构

FHIRValidation
├─ ExampleIG                        
│  ├─ ig.ini
│  ├─ input
│  │  ├─ fsh
│  │  │  ├─ alias.fsh
│  │  │  ├─ codesystems.fsh
│  │  │  ├─ organization.fsh
│  │  │  └─ valuesets.fsh
│  │  └─ pagecontent
│  │     └─ index.md
│  └─ sushi-config.yaml
├─ README.md
├─ README_zh.md
├─ TestCases
│  └─ FHIR Profile-based Validation  testcases.postman_collection.json
├─ docker-compose.yml
└─ image-iris
   ├─ Dockerfile
   └─ src
      ├─ FullIG
      ├─ IGPackages
      │  ├─ hl7.fhir.uv.extensions.r4#5.1.0.tgz
      │  ├─ hl7.terminology.r4#6.0.2.tgz
      │  └─ package.tgz
      └─ init
         └─ init.sh

ExampleIG

该子目录下的所有文件为本项目所采用的自定义FHIR规范SUSHI源码,供用户定义FHIR规约时参考使用。

TestCases

该子目录下存放基于FHIR REST API的测试用例脚本,需导入到Postman中使用

image-iris

该子目录下存放nterSystems IRIS for Health镜像所需的文件,其中: └─ src ├─ FullIG 该目录中存放SUSHI生成的自定义FHIR IG ├─ IGPackages 该目录中存放自定义FHIR IG的 package 文件 └─ init 该目录中存放IRIS的Docker镜像初始化脚本

FHIR package简介

HL7组织推荐使用实施指南(Implementation Guild)来解释如何使用FHIR规范。除用于开发人员阅读的说明(如html)外,实施指南中通常也包括可直接被机器读取和应用的工件(artifacts),可被用于驱动代码生成和数据验证等任务。
FHIR实施指南采用NPM Package规范管理依赖。指南涉及的所有StructureDefinition,ValueSet等资源将被打包在一块,形成可被FHIR Server用于读取规范,生成客户端代码或执行数据质量校验的资源包。
通过SUSHI工具生成的实施指南中就包含若干package文件。如本例中,image-iris/src/IGPackages/package.tgz即为生成的package包,可被IRIS FHIR Server直接导入使用。应当注意到的是,除核心资源包(如hl7.fhir.r4.core)外,完整的FHIR规范还需要引用术语、扩展等额外的资源包。
目前FHIR规范引用机制的文档尚不完善。如基于R4版的FHIR规范除引用hl7.fhir.r4.core外,还需引用hl7.fhir.uv.extensions.r4#5.1.0hl7.terminology.r4#6.0.2,但这些引用关系在R5版本中方有记录,在R4版文档中并未完整声明,需开发者在开发过程中自行补充。
在本例中这些包已下载在image-iris/src/IGPackages文件夹下,将作为依赖在自定义FHIR实施指南之前加载。

FHIR validation简介

参见FHIR规范Validating Resources一节。FHIR规范已经设计了对数据结构、属性基数、值域、代码绑定和约束等一系列机制在内的数据质量校验机制。HL7组织在FHIR规范中并未强制要求遵循何种强度的质量控制,但建议采用宽进严出的原则处理FHIR数据。
对于保存FHIR资源的FHIR存储库而言,保障FHIR资源的数据质量是使医疗行业具有价值,保障医疗行为安全性的前提条件。因此,在构建基于FHIR存储的数据共享交换方案时,即使不得不保存不满足数据质量要求的数据,也应对其进行校验,标识不符合项,推动数据治理活动的进行,从而保障医疗安全和数据消费者的利益。
在FHIR规范指出的多种数据校验方式中,FHIR Validator和FHIR操作对数据质量校验的覆盖最为全面。
本例将使用InterSystems IRIS for Health所提供的$validate操作,通过profile参数对尚未保存的FHIR数据进行校验。使用者也可修改测试用例,构建HTTP POST参数,对存量FHIR资源进行校验。
还应当注意的是,$validate操作如被正确调用,将通过Http 200返回校验结果,如有不符合项,将在返回的OperationOutcome资源中包裹错误信息,而不通过Http代码标识错误。

对FHIR的扩展

在本例中基于FHIR R4对Organization资源进行了如下扩展:

1. 修改language的绑定强度

将机构主要语言的绑定强度修改为required

2. active字段基数从0..1改为1..1

从而使得数据的状态成为必填字段,有且只有一个元素

3. name字段基数从0..1改为1..1

组织机构名称成为必填字段,有且只有一个元素。参考我国医院除医院名称外,如果具备急救中心、胸痛中心等牌照,还可能具有多个名称。但因注意到,这些牌照通常标识了医疗机构提供的服务能力,而非在组织机构注册系统中具备的法定名称,且此类牌照生命周期与医疗机构自身的生命周期并不一致。因此,从牌照获得的名称宜视为该医疗机构的服务能力而非机构的唯一名称。在FHIR中,通过服务能力获得的名称可通过资源HealthcareService提供,该资源与Organization资源间可建立多对一的引用关系,更适合用来表达上述概念。

4. 增加医疗机构的组织机构类型

根据中国国家标准GB/T 20091-2021 组织机构类型,分别增加了CodeSystem organizationtype-code-system和ValueSet organizationtype-vs,并通过Extension向Organization资源中添加了扩展mdm-organizationTypeExtension,从而使得该资源可用于表示表示标识中国组织机构类型。
该扩展通过对Extension切片实现,且基数为1..1,即医疗机构资源必须具有组织机构类型元素。

5. 约束医疗机构证件号码

FHIR基础标准并未纳入中国组织机构统一社会信用代码的证件类型,为此增加了CodeSystem cs-identifierType-code-system,并对Identifier按其类型进行了切片,使之必须可以表达社会信用代码。且社会信用代码的格式遵循以下约束:

  1. identifier.use必须取值为official,即正式/官方用途
  2. identifier.type必须遵循cs-identifierType-code-system要求,system必须为该codesystem的uri,code必须为“USCC”
  3. identifier.value必须遵循自定义约束uscc-length-18,该字段长度必须为18位,其中前17位必须为数字,最后1位必须为数字或字母

测试用例列表

1. Without profile - All OK

未声明资源对应的profile,因此FHIR Server将不对资源中各属性的值进行校验,仅返回All OK。

2. Unknow field

在资源中加入了未被定义的属性isNational,因此校验引擎返回了Unrecognized element错误。

3. Wrong cardinality - less

在本IG中,修改了Organization资源name属性的基数为1..1,即应有且仅有一个组织机构名称。本测试用例未填写名称,因此数据校验失败。 另外,可以观察到Identifier.type经过扩展,加入了统一社会信用代码作为标识符类型,FHIR R4规范里并不包含这个值,但该字段的代码绑定强度仅为example,不强制约束。因此校验引擎返回了information级的值域代码不符合信息而没有报错。

4. Binding strength

在本IG中,组织机构的language属性的代码绑定强度改为了required,则该字段值域必须符合http://hl7.org/fhir/ValueSet/languages,因此,当该字段取值为wrong language时,因不在required值域中,将导致error级错误

5. Wrong value

在本IG中,组织机构类型的值域来自于organizationtype-code-system,因此,当类型为mdm-organizationTypeExtension的extension元素中code的值为“999”,不在值域中时,将导致error级错误

6. Failing invariant

在本IG中,组织机构的社会信用代码必须遵循自定义约束uscc-length-18(该字段长度必须为18位,其中前17位必须为数字,最后1位必须为数字或字母),因此,当其末位为字符“%”时,违反该约束,将导致error级错误

7. Failing profile

对于一个资源定义的一个profile包含了多个约束,因此,在校验时所有不满足profile的问题都将被检出,例如本例中:

  1. 错误的language代码
  2. 错误的组织机构类型
  3. 缺少name字段 可见上述问题都被检出
0
0 127
文章 Michael Lei · 九月 27, 2024 11m read

欢迎来到我的 CI/CD 系列的下一个章节,我们将探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。

今天,我们来谈谈互操作性。

问题

当您有一个有效的互操作性生产时,您有两个独立的流程:一个是处理消息的可以正常运行的生产流程,另一个是更新代码、生产配置和系统默认设置的 CI/CD 流程。

显然,CI/CD 流程会影响互操作性。 但问题是:

  • 更新期间究竟发生了什么?
  • 我们需要做些什么以在更新期间尽可能缩短或消除生产停机时间?

术语

  • 业务主机 (BH) – 互操作性生产的一个可配置元素:业务服务 (BS)、业务流程(BP、BPL)或业务操作 (BO)。
  • 业务主机作业 (Job) – 运行业务主机代码并由互操作性生产管理的 InterSystems IRIS 作业。
  • 生产 – 业务主机的互联集合。
  • 系统默认设置 (SDS) – 特定于安装 InterSystems IRIS 的环境的值。
  • 有效消息 – 当前正在由某个业务主机作业处理的请求。 一个业务主机作业最多只能有一条有效消息。 没有有效消息的业务主机作业处于空闲状态。

发生了什么?

我们从生产生命周期开始。

生产启动

首先,可以启动生产。 每个命名空间只能同时运行一个生产,通常而言(除非您真正知道自己在做什么以及为什么这样做),每个命名空间内只能运行一个生产。 不推荐在一个命名空间中于两个或多个不同的生产之间来回切换。 启动生产会启动生产中定义的所有已启用的业务主机。 某些业务主机启动失败不会影响生产启动。

提示:

  • 可以从系统管理门户或者调用以下代码来启动生产:##class(Ens.Director).StartProduction("ProductionName")
  • 通过实现 OnStart 方法可以在生产启动时(在启动任何业务主机作业之前)执行任意代码
  • 生产启动是一个可审核事件。 您始终可以在审核日志中查看是谁在何时启动的生产。

生产更新

在生产启动后,Ens.Director 会持续监视正在运行的生产。 生产存在两种状态:目标状态,在生产类和系统默认设置中定义;以及_运行状态_ – 当前运行的作业及其创建时应用的设置。 如果所需状态与当前状态相同,则一切正常;但如果有差异,就可以(也应该)更新生产。 通常,您会在系统管理门户的“生产配置”页面上看到一个红色的 Update 按钮。

更新生产意味着尝试使当前生产状态与目标生产状态匹配。

当您运行 ##class(Ens.Director).UpdateProduction(timeout=10, force=0) 以更新生产时,它会为每个业务主机执行以下操作:

  1. 将有效设置与生产/SDS/类设置进行比较
  2. 当且仅当 (1) 显示不匹配时,业务主机会被标记为过时并需要更新。

为每个业务主机运行此操作后,UpdateProduction 会构建一组更改:

  • 要停止的业务主机
  • 要启动的业务主机
  • 要更新的生产设置

然后,应用这些更改。

通过这种方式,“更新”设置而不更改任何内容不会导致生产停机。

提示:

  • 可以从系统管理门户或者调用以下代码来更新生产:##class(Ens.Director).UpdateProduction(timeout=10, force=0)
  • 默认的系统管理门户更新超时为 10 秒。 如果您知道处理消息需要更长时间,请调用 Ens.Director:UpdateProduction 并设置更长的超时。
  • 更新超时是一个生产设置,您可以将其更改为更大的值。 此设置适用于系统管理门户。

代码更新

UpdateProduction 不会使用过时的代码更新 BH。 这是一种以安全为导向的行为,但如果您想要在底层代码更改时自动更新所有正在运行的 BH,请按以下步骤操作:

首先,按以下方式加载和编译:

do $system.OBJ.LoadDir(dir, "", .err, 1, .load)
do $system.OBJ.CompileList(load, "curk", .errCompile, .listCompiled)

现在,listCompiled 将包含由于 u 标志而实际编译的所有条目(使用 git 差异来尽可能减小加载集)。 使用此 listCompiled 获取所有已编译类的 $lb:

set classList = ""
set class = $o(listCompiled(""))
while class'="" { 
  set classList = classList _ $lb($p(class, ".", 1, *-1))
  set class=$o(listCompiled(class))
}

然后,计算需要重启的 BH 的列表:

SELECT %DLIST(Name) bhList
FROM Ens_Config.Item 
WHERE 1=1
  AND Enabled = 1
  AND Production = :production
  AND ClassName %INLIST :classList

最后,在获取 bhList 后,停止并启动受影响的主机:

for stop = 1, 0 {
  for i=1:1:$ll(bhList) {
    set host = $lg(bhList, i)
    set sc = ##class(Ens.Director).TempStopConfigItem(host, stop, 0)
  }
  set sc = ##class(Ens.Director).UpdateProduction()
}

生产停止

生产可以停止,这意味着向所有业务主机作业发送关闭请求(如果存在,则在它们处理完有效消息后安全地关闭)。

提示:

  • 可以从系统管理门户或者调用以下代码来停止生产:##class(Ens.Director).StopProduction(timeout=10, force=0)
  • 默认的系统管理门户停止超时为 120 秒。 如果您知道处理消息需要更长时间,请调用 Ens.Director:StopProduction 并设置更长的超时。
  • 关闭超时是一个生产设置。 您可以将其更改为更大的值。 此设置适用于系统管理门户。
  • 通过实现 OnStop 方法可以在生产停止时执行任意代码
  • 生产停止是一个可审核事件,您始终可以在审核日志中查看是谁在何时停止的生产。

重要的一点是,生产是业务主机的总和:

  • 启动生产意味着启动所有已启用的业务主机。
  • 停止生产意味着停止所有正在运行的业务主机。
  • 更新生产意味着计算出过时的业务主机的子集,因此首先停止它们,然后立即重新启动。 此外,新增的业务主机只会启动,从生产中删除的业务主机只会停止。

这会将我们带到业务主机的生命周期。

业务主机启动

业务主机由相同的业务主机作业组成(根据池大小设置的值)。 启动业务主机意味着启动所有业务主机作业。 它们会并行启动。

单个业务主机作业的启动方式如下:

  1. 互操作性作业是一个将成为业务主机作业的新进程。
  2. 新进程注册为互操作性作业。
  3. 业务主机代码和适配器代码加载到进程内存中。
  4. 与业务主机和适配器相关的设置加载到内存中。 优先级顺序如下: a. 生产设置(覆盖系统默认设置和类设置)。 b. 系统默认设置(覆盖类设置)。 c. 类设置。
  5. 作业准备就绪并开始接受消息。

在完成 (4) 后,作业无法更改设置或代码,因此当您导入新的/相同的代码和新的/相同的系统默认设置时,它不会影响当前正在运行的互操作性作业。

业务主机停止

停止业务主机作业意味着:

  1. 互操作性命令作业停止接受更多消息/输入。
  2. 如果存在有效消息,业务主机作业具有一定的超时秒数来处理该消息(完成消息会结束 BO 的 OnMessage 方法,BS 的 OnProcessInput,BPL BP 的状态 S<int> 方法以及 BP 的 On* 方法)。
  3. 如果在超时前有效消息未被处理且 force=0,则生产更新会在该业务主机上失败(您会在 SMP 中看到一个红色的 Update 按钮)。
  4. 如果此列表中的任何一项为真,则表示停止成功:
    • 没有有效消息
    • 有效消息在 timeout 之前处理完
    • 有效消息在超时之前未处理完但 force=1
  5. 作业的互操作性取消注册并停止。

业务主机更新

业务主机更新意味着停止当前运行的业务主机作业并启动新作业。

业务规则、路由规则和 DTL

所有业务主机会在新版本的业务规则、路由规则和 DTL 可用时立即开始使用它们。 在这种情况下,不需要重启业务主机。

离线更新

不过,有时生产更新需要单个业务主机停机。

规则取决于新代码

考虑接下来的情况。 您有一个当前的路由规则 X,它根据任意标准将消息路由到业务流程 A 或 B。 在新提交中,您同时添加:

  • 业务流程 C
  • 新版本的路由规则 X,可以将消息路由到 A、B 或 C。

在此场景下,您不能先加载规则,然后再更新生产。 原因在于,新编译的规则会立即开始将消息路由到业务流程 C,而 InterSystems IRIS 可能尚未编译该规则,或者互操作性尚未更新以供使用。 在这种情况下,您需要禁用包含路由规则的业务主机,更新代码,更新生产,然后再次启用业务主机。

注:

  • 如果您使用生产部署文件更新生产,它会自动禁用/启用所有受影响的 BH。
  • 对于 InProc 调用的主机,编译会使调用方持有的特定主机的缓存失效。

业务主机之间的依赖关系

业务主机之间的依赖关系至关重要。 假设您有业务流程 A 和 B,其中 A 向 B 发送消息。 在新提交中,您同时添加:

  • 新版本的流程 A,可以在向 B 发送的请求中设置新属性 X
  • 新版本的流程 B,可以处理新属性 X

在此场景中,我们必须首先更新流程 B,然后更新流程 A。 您可通过以下两种方式之一完成此操作:

  • 在更新期间禁用业务主机
  • 将更新拆分为两步:首先,仅更新流程 B,然后在单独的更新中开始从流程 A 向流程 B 发送消息。

这个主题一个更具挑战性的变体是,新版本的流程 A 和流程 B 与旧版本不兼容,这需要业务主机停机。

队列

如果您知道更新后某个业务主机将无法处理旧消息,则需要确保在更新前该业务主机队列为空。 为此,请禁用所有向该业务主机发送消息的业务主机,并等到其队列变空。

BPL 业务流程中的状态更改

首先,简单介绍一下 BPL BP 的运作方式。 在您编译 BPL BP 后,会在与完整 BPL 类同名的软件包中创建两个类:

  • Thread1 类包含方法 S1、S2、... SN,对应于 BPL 中的活动
  • Context 类包含所有上下文变量,以及 BPL 将执行的下一个状态(即 S5

此外,BPL 类是持久类,可以存储当前正在处理的请求。

BPL 的工作方式是在 Thread 类中执行 S 方法并相应地更新 BPL 类表、Context 表和 Thread1 表,其中一条“正在处理”的消息是 BPL 表中的一行。 请求处理完后,BPL 会删除 BPL、ContextThread 条目。 由于 BPL BP 是异步的,通过在 S 调用之间保存信息并在不同请求之间切换,一个 BPL 作业可同时处理多个请求。 例如,BPL 处理一个请求,直至其到达 sync 活动 – 等待来自 BO 的回答。 它会将当前上下文保存到磁盘中,同时将 %NextState 属性(位于 Thread1 类中)设置为响应活动 S 方法,并在 BO 回答前继续处理其他请求。 BO 回答后,BPL 会将上下文加载到内存中,并执行与 %NextState 属性中保存的状态对应的方法。

现在,当我们更新 BPL 时会发生什么? 首先,我们需要检查是否至少满足以下两个条件之一:

  • 更新期间,上下文表为空,这意味着没有正在处理的有效消息。
  • 新状态与旧状态相同,或者新状态是在旧状态之后添加的。

如果至少满足一个条件,我们就可以开始更新。 要么没有需要更新后 BPL 处理的更新前请求,要么状态是在结束时添加的,这意味着旧请求也可以进入其中(假设更新前请求与更新后 BPL 活动和处理兼容)。

但是,如果您有正在处理的有效请求,而 BPL 更改了状态顺序,该怎么办? 理想情况下,如果您可以等待,则禁用 BPL 调用方并等待队列为空。 验证上下文表是否也为空。 请记住,队列只显示未处理的请求,而上下文表存储正在处理的请求,因此您可能会遇到一个非常繁忙的 BPL 显示零队列大小的情况,这是正常的。 之后,禁用 BPL,执行更新并启用所有之前禁用的业务主机。

如果无法实现(通常是在 BPL 非常长的情况下,例如,我记得我更新过一个花了大约一周时间处理请求的 BPL,或者更新窗口太短),请使用 BPL 版本控制

或者,您也可以编写一个更新脚本。 在此更新脚本中,将旧的后续状态映射到新的后续状态并在 Thread1 表上运行,以便更新的 BPL 可以处理旧请求。 当然,在更新期间必须禁用 BPL。 也就是说,这是一种极为罕见的情况,通常您不必这样做,但如果您需要这样做,方法就是如此。

结论

为了将底层代码更改后实现生产所需的操作数降到最低,互操作性实现了一种复杂的算法。 每次 SDS 更新时,调用具有安全超时的 UpdateProduction。 对于每次代码更新,您都需要决定一种更新策略。

通过使用 git 差异减少编译的代码量有助于缩短编译时间,但利用自身“更新”代码并重新编译它或使用相同的值“更新”设置不会触发或要求进行生产更新。

更新和编译业务规则、路由规则和 DTL 可使它们在未进行生产更新的情况下立即可用。

最后,生产更新是一项安全操作,通常不需要停机。

链接

在本文撰写期间,@James MacKeith@Dmitry Zasypkin@Regilo Regilio Guedes de Souza 提供了宝贵的帮助,作者对此深表感谢。

0
0 117
文章 Michael Lei · 九月 27, 2024 9m read

在这一系列文章中,我想向大家介绍并探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我将介绍以下主题:

  • Git 101
  • Git 流程(开发流程)
  • GitLab 安装
  • GitLab 工作流
  • 持续交付
  • GitLab 安装和配置
  • GitLab CI/CD
  • 为何使用容器
  • 容器基础架构
  • 使用容器的 CD

第一篇文章中,我们介绍了 Git 基础知识、深度理解 Git 概念对现代软件开发至关重要的原因,以及如何使用 Git 开发软件。

第二篇文章中,我们介绍了 GitLab 工作流 – 一个完整的软件生命周期流程,并介绍了持续交付。

第三篇文章中,我们介绍了 GitLab 安装和配置以及将环境连接到 GitLab

第四篇文章中,我们编写了 CD 配置。

第五篇文章中,我们讨论了容器以及使用容器的方式(和原因)。

第六篇文章中,我们将探讨运行包含容器的持续交付管道所需的主要组件以及这些组件如何协同运行。

在这篇文章中,我们将构建上一篇文章中探讨的持续交付配置。

工作流

在持续交付配置中,我们会:

  • 将代码推送到 GitLab 仓库
  • 构建 docker 镜像
  • 进行测试
  • 将镜像发布到 docker 注册表
  • 将旧容器换为注册表中的新版本

也可以用示意图形式表示此流程:

我们开始吧。

构建

首先,我们需要构建镜像。

我们的代码通常存储在仓库中,CD 配置位于 gitlab-ci.yml 中,但为了提高安全性,我们会在构建服务器上存储几个服务器特定的文件。

GitLab.xml

包含 CD 挂钩代码。 该代码是在上一篇文章中开发的,并在 GitHub 上提供。 这是一个小型库,用于加载代码、运行各种挂钩以及测试代码。 作为更好的替代方案,您可以使用 git 子模块将此项目或类似项目包含到您的仓库中。 最好选择子模块,因为子模块更容易保持最新状态。 另一个替代方案是在 GitLab 上为版本添加标签,并使用 ADD 命令加载这些版本。

iris.key

许可证密钥。 或者,它可以在容器构建过程中下载,而不是存储在服务器上。 将密钥存储在仓库中非常不安全。

pwd.txt

包含默认密码的文件。 将这类文件存储在仓库中也非常不安全。 此外,如果您在单独的服务器上托管生产环境,它可能有不同的默认密码。

load_ci.script

初始脚本,它执行以下任务:

  • 实现操作系统身份验证
  • 加载 GitLab.xml
  • 初始化 GitLab 实用工具设置
  • 加载代码

set sc = ##Class(Security.System).Get("SYSTEM",.Properties) write:('sc) $System.Status.GetErrorText(sc) set AutheEnabled = Properties("AutheEnabled") set AutheEnabled = $zb(+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

现在,继续持续交付配置:

构建镜像:
  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 .

这里会执行哪些操作?

首先,由于 docker build 只能访问基础构建目录的子目录(本例中是仓库根目录),我们需要将“秘密”目录(包含 GitLab.xmliris.keypwd.txt 和 load_ci.script 的目录)复制到克隆的仓库中。

接下来,首次终端访问需要用户名/密码,因此我们会将这些信息添加到 load_ci.script 中(这也是 load_ci.script 开头一行留空的原因)。

最后,我们会构建 docker 镜像并适当地为其添加标签:docker.domain.com/test/docker:$CI_COMMIT_REF_NAME

其中,$CI_COMMIT_REF_NAME 是当前分支的名称。 请注意,镜像标签的第一部分应与 GitLab 中的项目名称相同,这样才能在 GitLab 的“注册表”标签页中看到它(“注册表”标签页中提供了关于添加标签的说明)。

Dockerfile

构建 docker 镜像是通过 Dockerfile 完成的,具体如下:

FROM docker.intersystems.com/intersystems/iris:2018.1.1.611.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

我们从基本的 iris 容器开始。

首先,我们将仓库(和“秘密”目录)复制到容器中。

接下来,我们将许可证密钥和 GitLab.xml 复制到 mgr 目录中。

然后,我们将密码更改为 pwd.txt 中的值。 请注意,此操作会删除 pwd.txt。

之后,实例启动并执行 load_ci.script。

最后,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.611.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

请注意,我使用的是 GitLab Shell 执行器,而不是 Docker 执行器。 当您需要从镜像内部提取某些内容时,将使用 Docker 执行器,例如在 Java 容器中构建 Android 应用程序并且只需要一个 apk 时。 在我们的示例中,我们需要整个容器,因此需要使用 Shell 执行器。 因此,我们通过 GitLab Shell 执行器运行 Docker 命令。

 

运行

我们构建了镜像,接下来要运行镜像。如果是功能分支,我们销毁旧容器并启动新容器即可。 如果是环境,我们可以运行临时容器,在测试成功的情况下,可以替换环境容器(此内容留给读者作为练习)。

脚本如下。

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 尝试停止/移除不存在的容器,则会失败)。

接下来,我们启动新镜像并将其注册为环境。 Nginx 容器 使用 VIRTUAL_HOST 环境变量和 expose 指令(用于了解要代理的端口)自动代理请求。

run image:
  stage: run
  environment:
    name: $CI_COMMIT_REF_NAME
    url: http://$CI_COMMIT_REF_SLUG. docker.domain.com/index.html
  tags:
    - test
  script:
    - docker run -d
      --expose 52773
      --env VIRTUAL_HOST=$CI_COMMIT_REF_SLUG.docker.eduard.win
      --name iris-$CI_COMMIT_REF_NAME
      docker.domain.com/test/docker:$CI_COMMIT_REF_NAME
      --log $ISC_PACKAGE_INSTALLDIR/mgr/messages.log

 

测试

我们来运行一些测试。

test image:
  stage: test
  tags:
    - test
  script:
    - docker exec iris-$CI_COMMIT_REF_NAME irissession iris -U USER "##class(isc.git.GitLab).test()"

发布

最后,我们将镜像发布到注册表中

publish image:
  stage: publish
  tags:
    - test
  script:
    - docker login docker.domain.com -u dev -p 123
    - docker push docker.domain.com/test/docker:$CI_COMMIT_REF_NAME

可以使用 GitLab 秘密变量传递用户名/密码。

现在,我们可以在 GitLab 中看到该镜像:

其他开发者可以从注册表中拉取该镜像。 “环境”标签页上提供了我们所有的环境,可以轻松进行浏览:

 

结论

在这一系列文章中,我介绍了持续交付的常规方式。 这是一个涉及面非常广的话题,您应将这一系列文章视为方法集合,而不是确定性内容。 如果您想自动构建、测试和交付应用程序,可以选择持续交付(一般情况)和 GitLab(特殊情况)。 利用持续交付和容器,您可以根据需要自定义工作流。

链接

后续内容

就是这些! 希望我已介绍了持续交付和容器的基础知识。

但还有一些主题我没有介绍(可能后面会介绍),特别是关于容器的内容:

  • 数据可以在容器外持久保持,以下是相关文档
  • kubernetes 等编排平台
  • InterSystems Cloud Manager
  • 环境管理 – 创建临时环境以进行测试,在功能分支合并后移除旧环境
  • Docker compose 可以实现多容器部署
  • 缩减 docker 镜像大小并缩短构建时间
  • ...
0
0 110
文章 Michael Lei · 九月 27, 2024 8m read

在这一系列文章中,我想向大家介绍并探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我将介绍以下主题:

  • Git 101
  • Git 流程(开发流程)
  • GitLab 安装
  • GitLab 工作流
  • 持续交付
  • GitLab 安装和配置
  • GitLab CI/CD

第一篇文章中,我们介绍了 Git 基础知识、深度理解 Git 概念对现代软件开发至关重要的原因,以及如何使用 Git 开发软件。

第二篇文章中,我们介绍了 GitLab 工作流 – 一个完整的软件生命周期流程,并介绍了持续交付。

第三篇文章中,我们介绍了 GitLab 安装和配置以及将环境连接到 GitLab

在这篇文章中,我们将介绍编写 CD 配置。

计划

环境

首先,我们需要多个环境以及与之对应的分支:

环境分支交付有权提交的角色有权合并的角色
测试master自动开发者、所有者开发者、所有者
预生产preprod自动所有者
生产prod半自动(按下按钮进行交付)

所有者

开发周期

作为示例,我们将使用 GitLab 流程开发一个新功能,并使用 GitLab CD 进行交付。

  1. 在功能分支中开发功能。
  2. 对功能分支进行审查并将其合并到 master 分支中。
  3. 一段时间(合并了多个功能)后,将 master 分支合并到 preprod 分支中
  4. 一段时间(用户测试等)后,将 preprod 分支合并到 prod 分支中

具体如下图所示(我用草图标出了我们需要为 CD 开发的部分):

  1. 开发和测试
    • 开发者将新功能的代码提交到单独的功能分支中
    • 功能稳定后,开发者将功能分支合并到 master 分支中
    • 来自 master 分支的代码被交付到测试环境,在其中进行加载和测试
  2. 交付到预生产环境
    • 开发者创建从 master 分支到 preprod 分支的合并请求
    • 仓库所有者在一段时间后批准合并请求
    • 来自 preprod 分支的代码被交付到预生产环境
  3. 交付到生产环境
    • 开发者创建从 preprod 分支到 prod 分支的合并请求
    • 仓库所有者在一段时间后批准合并请求
    • 仓库所有者按下“部署”按钮
    • 来自 prod 分支的代码被交付到生产环境

也可以用示意图形式表示此流程:

应用程序

应用程序由两部分组成:

  • 在 InterSystems 平台上开发的 REST API
  • 客户端 JavaScript web 应用程序

阶段

通过上面的计划,我们可以确定需要在持续交付配置中定义的阶段:

  • 加载 – 将服务器端代码导入 InterSystems IRIS
  • 测试 – 测试客户端和服务器代码
  • 封装 – 构建客户端代码
  • 部署 – 使用 Web 服务器“发布”客户端代码

以下是它在 gitlab-ci.yml 配置文件中的样式:

stages:
  - load
  - test
  - package
  - deploy

脚本

加载

下面我们来定义脚本。 脚本文档。 我们先来定义用于加载服务器端代码的脚本 load server

load server:
  environment:
    name: test
    url: http://test.hostname.com
  only:
    - master
  tags:
    - test
  stage: load
  script: csession IRIS "##class(isc.git.GitLab).load()"

脚本会执行哪些操作?

  • load server 是脚本名称
  • 接下来,我们来描述此脚本运行的环境
  • only: master – 告知 GitLab 此脚本仅应在向 master 分支进行提交时运行
  • tags: test 指定此脚本仅应在具有 test 标签的运行程序上运行
  • stage 指定脚本的阶段
  • script 定义要执行的代码 在本例中,我们从 isc.git.GitLab 类调用类方法 load

重要说明

对于 InterSystems IRIS,请将 csession 替换为 iris session

对于 Windows,请使用:irisdb -s ../mgr -U TEST "##class(isc.git.GitLab).load()

现在,我们来编写相应的 isc.git.GitLab 类。 此类中的所有入口点如下所示:

ClassMethod method()
{
    try {
        // code
        halt
    } catch ex {
        write !,$System.Status.GetErrorText(ex.AsStatus()),!
        do $system.Process.Terminate(, 1)
    }
}

请注意,可以通过两种方式结束此方法:

  • 停止当前进程 – 在 GitLab 中注册为成功完成
  • 调用  $system.Process.Terminate – 异常终止进程,GitLab 将此情况注册为错误

因此,加载代码如下:

/// Do a full load
/// do ##class(isc.git.GitLab).load()
ClassMethod load()
{
    try {
        set dir = ..getDir()
        do ..log("Importing dir " _ dir)
        do $system.OBJ.ImportDir(dir, ..getExtWildcard(), "c", .errors, 1)
        throw:$get(errors,0)'=0 ##class(%Exception.General).%New("Load error")
    halt
} catch ex {
    write !,$System.Status.GetErrorText(ex.AsStatus()),!
    do $system.Process.Terminate(, 1)
}

}

调用了两个实用方法:

  • getExtWildcard – 获取相关文件扩展名列表
  • getDir – 获取仓库目录

如何获取目录?

执行脚本时,GitLab 会先指定很多环境变量。 其中一个环境变量是 CI_PROJECT_DIR – 克隆仓库以及运行作业位置的完整路径。 我们可以通过 getDir 方法轻松获取:

ClassMethod getDir() [ CodeMode = expression ]
{
##class(%File).NormalizeDirectory($system.Util.GetEnviron("CI_PROJECT_DIR"))
}


测试

以下是测试脚本:

load test:
  environment:
    name: test
    url: http://test.hostname.com
  only:
    - master
  tags:
    - test
  stage: test
  script: csession IRIS "##class(isc.git.GitLab).test()"
  artifacts:
    paths:
      - tests.html

有哪些更改? 当然是名称和脚本代码,但还添加了工件。 工件是作业成功完成后附加到作业的文件和目录列表。 本例中,测试完成后,我们可以生成重定向到测试结果的 HTML 页面,并使其可以通过 GitLab 访问。

请注意,加载阶段有很多复制粘贴的内容 – 环境是相同的,脚本部分(例如环境)可以单独标记并附加到脚本。 我们来定义测试环境:

.env_test: &env_test
  environment:
    name: test
    url: http://test.hostname.com
  only:
    - master
  tags:
    - test

现在,我们的脚本如下:

load test:
  <<: *env_test
  script: csession IRIS "##class(isc.git.GitLab).test()"
  artifacts:
    paths:
      - tests.html

接下来,我们使用 UnitTest 框架执行测试。

/// do ##class(isc.git.GitLab).test()
ClassMethod test()
{
    try {
        set tests = ##class(isc.git.Settings).getSetting("tests")
        if (tests'="") {
            set dir = ..getDir()
            set ^UnitTestRoot = dir
        $$$TOE(sc, ##class(%UnitTest.Manager).RunTest(tests, "/nodelete"))
        $$$TOE(sc, ..writeTestHTML())
        throw:'..isLastTestOk() ##class(%Exception.General).%New("Tests error")
    }
    halt
} catch ex {
    do ..logException(ex)
    do $system.Process.Terminate(, 1)
}

}

本例中,测试设置是相对于存储单元测试的仓库根目录的路径。 如果此处为空,则跳过测试。 writeTestHTML 方法用于输出重定向到测试结果的 html:

ClassMethod writeTestHTML()
{
    set text = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(), "html").Data.Read()
    set text = $replace(text, "!!!", ..getURL())
set file = ##class(%Stream.FileCharacter).%New()
set name = ..getDir() _  "tests.html"
do file.LinkToFile(name)
do file.Write(text)
quit file.%Save()

}

ClassMethod getURL() { set url = ##class(isc.git.Settings).getSetting("url") set url = url _ $system.CSP.GetDefaultApp("%SYS") set url = url_"/%25UnitTest.Portal.Indices.cls?Index="_ $g(^UnitTest.Result, 1) _ "&$NAMESPACE=" _ $zconvert($namespace,"O","URL") quit url }

ClassMethod isLastTestOk() As %Boolean { set in = ##class(%UnitTest.Result.TestInstance).%OpenId(^UnitTest.Result) for i=1:1:in.TestSuites.Count() { #dim suite As %UnitTest.Result.TestSuite set suite = in.TestSuites.GetAt(i) return:suite.Status=0 $$$NO } quit $$$YES }

XData html { <html lang="en-US"> <head> <meta charset="UTF-8"/> <meta http-equiv="refresh" content="0; url=!!!"/> <script type="text/javascript"> window.location.href = "!!!" </script> </head> <body> If you are not redirected automatically, follow this <a href='!!!'>link to tests</a>. </body> </html> }

封装

我们的客户端是一个简单的 HTML 页面:

<html>
<head>
<script type="text/javascript">
function initializePage() {
  var xhr = new XMLHttpRequest();
  var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/version";
  xhr.open("GET", url, true);
  xhr.send();
  xhr.onloadend = function (data) {
    document.getElementById("version").innerHTML = "Version: " + this.response;
  };

var xhr = new XMLHttpRequest(); var url = "${CI_ENVIRONMENT_URL}:57772/MyApp/author"; xhr.open("GET", url, true); xhr.send(); xhr.onloadend = function (data) { document.getElementById("author").innerHTML = "Author: " + this.response; }; } </script> </head> <body onload="initializePage()"> <div id = "version"></div> <div id = "author"></div> </body> </html>

要进行构建,需要将 ${CI_ENVIRONMENT_URL} 替换为其值。 当然,实际应用程序可能需要 npm,但此处仅为了举例说明。 脚本如下:

package client:
  <<: *env_test
  stage: package
  script: envsubst < client/index.html > index.html
  artifacts:
    paths:
      - index.html

部署

最后,我们将 index.html 部署到 Web 服务器根目录,以部署客户端。

deploy client:
  <<: *env_test
  stage: deploy
  script: cp -f index.html /var/www/html/index.html

就是这些!

多个环境

如果您需要在多个环境中执行相同(相似)的脚本,应该如何操作? 脚本部分也可以是标签,因此下面给出了在测试和预生产环境中加载代码的示例配置:

stages:
  - load
  - test

.env_test: &env_test environment: name: test url: http://test.hostname.com only: - master tags: - test

.env_preprod: &env_preprod environment: name: preprod url: http://preprod.hostname.com only: - preprod tags: - preprod

.script_load: &script_load stage: load script: csession IRIS "##class(isc.git.GitLab).loadDiff()"

load test: <<: *env_test <<: *script_load

load preprod: <<: *env_preprod <<: *script_load

通过这种方式,我们便无需复制粘贴代码。

有关完整的 CD 配置,请参阅此处。 该配置遵循在测试、预生产和生产环境之间移动代码的原始计划。

结论

可以将持续交付配置为自动执行任何所需的开发工作流。

链接

后续内容

在下一篇文章中,我们将创建利用 InterSystems IRIS Docker 容器的 CD 配置。

0
0 104
文章 Michael Lei · 九月 26, 2024 4m read

在这一系列文章中,我想向大家介绍并探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我将介绍以下主题:

  • Git 101
  • Git 流程(开发流程)
  • GitLab 安装
  • GitLab 工作流
  • 持续交付
  • GitLab 安装和配置
  • GitLab CI/CD

第一篇文章中,我们介绍了 Git 基础知识、深度理解 Git 概念对现代软件开发至关重要的原因,以及如何使用 Git 开发软件。

第二篇文章中,我们介绍了 GitLab 工作流 – 一个完整的软件生命周期流程,并介绍了持续交付。

在这篇文章中,我们将探讨:

  • GitLab 安装和配置
  • 将环境连接到 GitLab

GitLab 安装

我们将在本地安装 GitLab。 可以通过多种方式安装 GitLab – 通过源代码、软件包安装,以及在容器中安装。 我不会在这里详细介绍所有步骤,请参阅相关指南。 但仍需要注意几点:

前提条件:

  • 单独的服务器 – 由于 GitLab 属于 Web 应用程序,并且需要占用大量资源,最好在单独的服务器上运行
  • Linux
  • (可选,但强烈建议采用)域 – 运行页面和保护整个安装时需要

配置

首先,您可能需要发送包含通知的电子邮件

接下来,建议安装 Pages。 正如我们在上一篇文章中所讨论的 – 可以将脚本中的工件上传到 GitLab。 用户可以下载这些工件,但能够直接在浏览器中打开工件将非常有用,为此我们需要使用页面。

需要使用页面的原因:

由于 html 页面会在加载时重定向,可以使用 html 页面将用户重定向到我们需要的位置。 例如,下列代码生成的 html 页面会将用户重定向到上次执行的单元测试(生成 html 时):

ClassMethod writeTestHTML()
{
  set text = ##class(%Dictionary.XDataDefinition).IDKEYOpen($classname(), "html").Data.Read()
  set text = $replace(text, "!!!", ..getURL())

set file = ##class(%Stream.FileCharacter).%New() set name = "tests.html" do file.LinkToFile(name) do file.Write(text) quit file.%Save() }

ClassMethod getURL() { set url = "http://host:57772" set url = url _ $system.CSP.GetDefaultApp("%SYS") set url = url_"/%25UnitTest.Portal.Indices.cls?Index="_ $g(^UnitTest.Result, 1) _ "&$NAMESPACE=" _ $zconvert($namespace,"O","URL") quit url }

XData html { <html lang="en-US"> <head> <meta charset="UTF-8"/> <meta http-equiv="refresh" content="0; url=!!!"/> <script type="text/javascript">window.location.href = "!!!"</script> </head> <body> If you are not redirected automatically, follow this <a href='!!!'>link to tests</a>. </body> </html> }

我使用页面时遇到了错误(浏览工件时出现 502 错误) - 解决方法

 

将环境连接到 GitLab

要运行 CD 脚本,需要使用环境,即配置好的用于运行应用程序的服务器。 假设有一个安装了 InterSystems 产品(例如 InterSystems IRIS,但也可以使用 Caché 和 Ensemble)的 Linux 服务器,可以通过以下步骤将环境连接到 GitLab:

  1. 安装 GitLab 运行程序
  2. 在 GitLab 中注册运行程序
  3. 允许运行程序调用 InterSystems IRIS
有关安装 GitLab 运行程序的

重要说明 - 安装 GitLab 运行程序后请勿克隆服务器。否则,结果将不可预测,并且大多数情况下的结果都不尽如人意。

在 GitLab 中注册运行程序

运行初始程序后:

sudo gitlab-runner register

您会看到多个提示,大部分步骤都非常简单,但有几步比较复杂:

请输入用于此运行程序的 gitlab-ci 令牌

有多个令牌可用:

  • 一个令牌用于整个系统(在管理设置中提供)
  • 一个令牌用于每个项目(在项目设置中提供)

由于您连接运行程序是为特定项目运行 CD,需要指定此项目的令牌。

请输入此运行程序的 gitlab-ci 标签(以逗号分隔):

在 CD 配置中,您可以筛选针对具体标签运行的脚本。 因此,在最简单的情况下,请指定一个将作为环境名称的标签。

请输入执行器:ssh、docker+machine、docker-ssh+machine、kubernetes、docker、parallels、virtualbox、docker-ssh、shell:
docker

如果您使用的是没有 docker 的普通服务器,请选择 shell。我们将在后续部分讨论 docker。

允许运行程序调用 InterSystems IRIS

将运行程序连接到 GitLab 后,我们需要允许运行程序与 InterSystems IRIS 交互,为此:

  1. gitlab-runner 用户应能够调用会话。 为此,请将该用户添加到 cacheusr 组:
    • usermod -a -G cacheusr gitlab-runner
  2. 在 InterSystems IRIS 中创建gitlab-runner 用户,并为其指定相应角色以执行 CD 任务(写入到数据库等)
  3. 允许进行操作系统级身份验证

对于 2 和 3,可以采用其他方式,例如传递用户名/密码,但我认为操作系统身份验证是更好的选择。

结论

在本部分中:

  • 我们安装了 GitLab
  • 将环境连接到 GitLab

后续内容

在下一部分中,我们将编写持续交付配置。

0
0 102
文章 Michael Lei · 九月 26, 2024 7m read

在这一系列文章中,我想向大家介绍并探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我将介绍以下主题:

  • Git 101
  • Git 流程(开发流程)
  • GitLab 安装
  • GitLab 工作流
  • 持续交付
  • GitLab 安装和配置
  • GitLab CI/CD

上一篇文章中,我们介绍了 Git 基础知识、深度理解 Git 概念对现代软件开发至关重要的原因,以及如何使用 Git 开发软件。 我们的侧重点仍是软件开发的实现部分,但本部分会介绍:

  • GitLab 工作流 - 从想法到用户反馈的完整软件生命周期流程
  • 持续交付 – 软件工程方式,团队通过这种方式在短周期内制作软件,从而确保软件可以随时实现可靠发布。 它的目的是更快速、更频繁地构建、测试和发布软件。

GitLab 工作流


GitLab 工作流是软件开发流程整个生命周期中可能采取的操作的逻辑序列。

GitLab 工作流会考虑我们在上一篇文章中探讨的 GitLab 流程。 具体如下:

  1. 想法:每个新提议都始于一个想法。
  2. 问题:探讨想法最有效的方法是为它创建问题。 您的团队和协作者可以在问题跟踪器中帮助您完善和改进问题。
  3. 计划:在讨论达成一致意见后,就可以开始编码了。 但首先,我们需要将问题指定至里程碑和问题看板,以此确定工作流的优先级并进行组织。
  4. 编码:现在,一切安排就绪后,我们就可以编写代码了。
  5. 提交:对草稿满意后,我们便可将代码提交到具有版本控制的功能分支。 上一篇文章详细介绍了 GitLab 流程。
  6. 测试:使用 GitLab CI 运行我们的脚本,以构建并测试应用程序。
  7. 审查:在脚本能够正常运行且测试和构建成功后,我们便可以让代码接受审查并获得批准。
  8. 暂存:现在应该将代码部署到暂存环境,以检查一切是否按预期进行,或者我们是否仍需要进行调整。
  9. 生产:如果一切顺利,便可将代码部署到生产环境!
  10. 反馈:现在可以回顾之前的流程,并检查有哪些阶段的工作需要改进。

再次说明,流程本身不是新的(或者 GitLab 独有的),并且可以通过其他工具来实现。

我们来讨论一下其中的几个阶段以及这些阶段涉及的内容。 还提供文档

问题和计划

GitLab 工作流的开始阶段以问题为中心,问题是指一个功能、一个错误或其他在语义上独立的工作。

问题有多个目的,例如:

  • 管理:问题具有截止日期、指定人员、用时和估计等, 可以帮助跟踪问题解决情况。
  • 行政管理:问题是里程碑的一部分,我们可以通过看板跟踪软件从一个版本过渡到另一个版本的进展。
  • 开发:问题具有与之相关的讨论和提交。

在计划阶段,我们可以按问题的优先级、里程碑、看板将问题分组,并获得问题的概览。

开发在前一部分进行了讨论,只需按照您希望使用的任何 git 流程执行操作即可。 我们开发了新功能并将其合并到 master 分支后,接下来应怎样操作?

持续交付

持续交付是一种软件工程方式,团队通过这种方式在短周期内制作软件,从而确保软件可以随时实现可靠发布。 它的目的是更快速、更频繁地构建、测试和发布软件。 这种方式允许对生产中的应用程序进行更多增量更新,从而帮助缩减交付更改的成本、缩短时间,以及降低风险。 简单且可重复的部署过程对于持续交付非常重要。

GitLab 中的持续交付

在 GitLab 中,持续交付配置按仓库以 YAML 配置文件形式定义。

  • 持续交付配置是一系列连续的阶段
  • 每个阶段都有一个或多个并行执行的脚本

脚本定义了一个操作以及执行该操作需要满足的条件:

  • 要执行的操作(运行 OS 命令、运行容器)?
  • 何时运行脚本:
    • 触发脚本的条件(特定分支)?
    • 之前的阶段失败时是否运行?
  • 手动运行还是自动运行?
  • 脚本在什么环境中运行?
  • 执行脚本后保存哪些工件(这些工件会从环境上传到 GitLab,以便轻松访问)?

环境是配置好的服务器或容器,可用于运行脚本。

运行程序用于在特定环境中执行脚本。 运行程序连接到 GitLab,并根据需要执行脚本。

运行程序可以部署在服务器上、容器上,甚至部署在本地机器上。

持续交付是如何实现的?

  1. 新提交推送到仓库中。
  2. GitLab 检查持续交付配置。
  3. 持续交付配置包含适用于所有情况的全部脚本,因此要过滤出应针对这一特定提交运行的一组脚本(例如提交到 master 分支仅会触发与 master 分支相关的操作)。 这组脚本称为管道
  4. 管道是在目标环境中执行的,执行结果会保存并显示在 GitLab 中。

例如,下面是提交到 master 分支后执行的一个管道:

管道包含四个阶段,各阶段连续执行

  1. 加载阶段会将代码加载到服务器中
  2. 测试阶段会运行单元测试
  3. 封装阶段包含两个并行运行的脚本:
    • 构建客户端
    • 导出服务器代码(主要用于提供信息)
  4. 部署阶段会将构建的客户端移动到 Web 服务器目录中。

我们可以看到,每个脚本都成功运行,如果其中一个脚本运行失败,则默认不会运行后面的脚本(但我们可以更改此行为):

如果我们打开脚本,可以查看日志并确定脚本运行失败的原因:

Running with gitlab-runner 10.4.0 (857480b6)
 on test runner (ab34a8c5)
Using Shell executor...
Running on gitlab-test...
Fetching changes...
Removing diff.xml
Removing full.xml
Removing index.html
Removing tests.html
HEAD is now at a5bf3e8 Merge branch '4-versiya-1-0' into 'master'
From http://gitlab.eduard.win/test/testProject
 * [new branch] 5-versiya-1-1 -> origin/5-versiya-1-1
 a5bf3e8..442a4db master -> origin/master
 d28295a..42a10aa preprod -> origin/preprod
 3ac4b21..7edf7f4 prod -> origin/prod
Checking out 442a4db1 as master...Skipping Git submodules setup$ csession ensemble "##class(isc.git.GitLab).loadDiff()"

[2018-03-06 13:58:19.188] Importing dir /home/gitlab-runner/builds/ab34a8c5/0/test/testProject/

[2018-03-06 13:58:19.188] Loading diff between a5bf3e8596d842c5cc3da7819409ed81e62c31e3 and 442a4db170aa58f2129e5889a4bb79261aa0cad0

[2018-03-06 13:58:19.192] Variable modified var=$lb("MyApp/Info.cls")

Load started on 03/06/2018 13:58:19 Loading file /home/gitlab-runner/builds/ab34a8c5/0/test/testProject/MyApp/Info.cls as udl Load finished successfully.

[2018-03-06 13:58:19.241] Variable items var="MyApp.Info.cls" var("MyApp.Info.cls")=""

Compilation started on 03/06/2018 13:58:19 with qualifiers 'cuk /checkuptodate=expandedonly' Compiling class MyApp.Info Compiling routine MyApp.Info.1 ERROR: MyApp.Info.cls(version+2) #1003: Expected space : '}' : Offset:14 [zversion+1^MyApp.Info.1] TEXT: quit, "1.0" } Detected 1 errors during compilation in 0.010s.

[2018-03-06 13:58:19.252] ERROR #5475: Error compiling routine: MyApp.Info.1. Errors: ERROR: MyApp.Info.cls(version+2) #1003: Expected space : '}' : Offset:14 [zversion+1^MyApp.Info.1] > ERROR #5030: An error occurred while compiling class 'MyApp.Info' ERROR: Job failed: exit status 1

编译错误导致脚本失败。

结论

  • GitLab 支持软件开发的所有主要阶段。
  • 持续交付可以帮助您自动执行软件构建、测试和部署任务。

后续内容

在下一篇文章中,我们将:

  • 安装 GitLab。
  • 将它连接到多个安装了 InterSystems 产品的环境。
  • 编写持续交付配置。

我们来探讨一下持续交付的运作方式。

首先,我们需要多个环境以及与之对应的分支。 代码进入此分支,并交付到目标环境:

环境分支交付有权提交的角色有权合并的角色
测试master自动开发者、所有者开发者、所有者
preprod预生产自动所有者
prod生产半自动(按下按钮进行交付)

所有者

作为示例,我们将使用 GitLab 流程开发一个新功能,并使用 GitLab CD 进行交付。

  1. 在功能分支中开发功能。
  2. 对功能分支进行审查并将其合并到 master 分支中。
  3. 一段时间(合并了多个功能)后,将 master 分支合并到 preprod 分支中
  4. 一段时间(用户测试等)后,将 preprod 分支合并到 prod 分支中

具体如下图所示:

  1. 开发和测试
    • 开发者将新功能的代码提交到单独的功能分支中
    • 功能稳定后,开发者将功能分支合并到 master 分支中
    • 来自 master 分支的代码被交付到测试环境,在其中进行加载和测试
  2. 交付到预生产环境
    • 开发者创建从 master 分支到 preprod 分支的合并请求
    • 仓库所有者在一段时间后批准合并请求
    • 来自 preprod 分支的代码被交付到预生产环境
  3. 交付到生产环境
    • 开发者创建从 preprod 分支到 prod 分支的合并请求
    • 仓库所有者在一段时间后批准合并请求
    • 仓库所有者按下“部署”按钮
    • 来自 prod 分支的代码被交付到生产环境

也可以用示意图形式表示此流程:

 

0
0 98
文章 Michael Lei · 九月 26, 2024 6m read

大家都搭建了测试环境。

有些人很幸运,可以在完全独立的环境中运行生产。

-- 佚名

.

在这一系列文章中,我想向大家介绍并探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。 我将介绍以下主题:

  • Git 101
  • Git 流程(开发流程)
  • GitLab 安装
  • GitLab 工作流
  • GitLab CI/CD
  • 包含容器的 CI/CD

第一部分将介绍现代软件开发的基础 – Git 版本控制系统和各种 Git 流程。

Git 101

虽然我们将主要探讨软件开发的概况以及 GitLab 如何帮助我们实现这一目标,但 Git,或者说 Git 设计中的几个基础的高级概念对于更好地理解后面的概念非常重要。

也就是说,Git 是基于这些概念的版本控制系统(还有更多概念,但这几个概念最为重要):

  • 非线性开发意味着,虽然我们的软件是从版本 1 到版本 2、再到版本 3 相继发布的,但实际上从版本 1 到版本 2 的升级是并行完成的 – 多名开发者会同时开发许多功能/错误修复。
  • 分布式开发意味着开发者独立于一个中央服务器或其他开发者,可以轻松地在自己的环境中进行开发。
  • 合并 – 基于前面提到的两个概念,我们会发现很多不同的版本同时存在,我们需要将它们统一成一个完整的状态。

我的意思不是说 Git 发明了这些概念。 Git 并没有发明这些概念, 而是使这些概念变得简单、流行,并加入了多个相关创新概念,也就是说,架构即代码/容器化改变了软件开发。

核心 git 术语

仓库是存储数据以及关于数据的元信息的项目。

  • “从物理层面来讲”,仓库是磁盘上的目录。
  • 仓库用于存储文件和目录。
  • 仓库还会存储每个文件的完整变更历史。

仓库可以:

  • 存储在您自己的计算机本地
  • 远程存储在远程服务器上

但从 git 的角度来看,本地仓库与远程仓库之间没有特殊的区别。

提交是仓库的固定状态。 很显然,如果每次提交都存储仓库的完整状态,我们的仓库很快就会变得非常大。 因此,提交会存储差异,也就是当前提交与其父提交之间的差异。

不同的提交可以具有不同数量的父提交:

  • 0 个 – 仓库中的第一个提交没有父提交。
  • 1 个 – 一切如常 - 我们的提交改变了仓库中的某些内容,就像在父提交期间一样
  • 2 个 – 当我们有两个不同的仓库状态时,我们可以将它们合并成一个新状态。 该状态和该提交就会有 2 个父提交。
  • >2 个 – 当我们将 2 个以上的不同仓库状态合并为一个新状态时,就会有 2 个以上的父提交。 这一概念与我们的讨论并没有特别大的关系,但它确实存在。

现在,对于父提交,每个与之不同的提交都被称为子提交。 每个父提交可以有任意数量的子提交。

分支是对提交的引用(或指针),如下图所示:

该图像显示的仓库具有两个提交(灰色圆圈),第二个圆圈是 master 分支的头部。 在我们添加更多提交后,仓库开始变成下图所示的状态:

这是最简单的情况。 我们的开发者一次负责处理一个更改。 但通常会有很多开发者同时负责处理不同的功能,我们需要使用提交树显示仓库中的变化。

提交树

我们从相同的起始状态开始。 仓库具有两个提交:

但现在,两名开发者在同时工作,为了避免相互干扰,他们在单独的分支中工作:

一段时间后,他们需要合并所做的更改,为此,他们创建了合并请求(也叫拉取请求), 顾名思义,该请求可将两个不同的仓库状态(本例中,我们要将 develop 分支合并到 master 分支中)合并为一个新状态。 接受相应审查并获得批准后,仓库状态如下图所示:

开发继续进行:

Git 101 - 总结

主要概念:

  • Git 是一个非线性的分布式版本控制系统。
  • 仓库用于存储数据以及关于数据的元信息。
  • 提交是仓库的固定状态。
  • 分支是对提交的引用。
  • 合并请求(也叫拉取请求)是将两个不同的仓库状态合并为一个新状态的请求。

如果您想了解更多关于 Git 的信息,可以阅读相关书籍

Git 流程

现在,读者已熟悉基本的 Git 术语和概念,我们来探讨一下如何使用 Git 管理软件生命周期的开发部分。很多实践(称为流程)介绍了使用 Git 的开发流程,但我们只会探讨其中两个:

  • GitHub 流程
  • GitLab 流程

GitHub 流程

GitHub 流程非常简单。 具体如下:

  1. 从仓库创建一个分支。
  2. 将更改提交到新分支
  3. 从您的分支发送一个拉取请求,其中包含您提议的更改,以发起讨论。
  4. 根据需要在您的分支上提交更多更改。 您的拉取请求将自动更新。
  5. 在分支准备好合并后,立即合并拉取请求。

我们需要遵守几条规则:

  • master 分支始终可部署(并且可正常运行!)
  • 不直接在 master 分支中进行开发
  • 在功能分支中进行开发
  • master 分支 == 生产* 环境**
  • 需要尽可能频繁地部署到生产环境

* 不要与“Ensemble 生产”混淆,这里的“生产”是指正式。

** 环境是配置好的代码运行位置,可以是服务器、虚拟机,甚至可以是容器。

如下图所示:

有关 GitHub 流程的更多信息,请参阅此处。 我们还提供了图解指南

GitHub 流程非常适合小型项目,如果您刚开始使用 Git 流程,可以尝试一下。 不过,GitHub 也会使用 GitHub 流程,因此也可以在大型项目中使用 GitHub 流程。

GitLab 流程

如果您还没有准备好立即部署到生产环境,GitLab 流程提供 GitHub 流程 + 环境。 具体做法是:在功能分支中进行开发(与上例相同),合并到 master 分支中(与上例相同),但这里有一个不同之处: master 分支仅等同于测试环境。 除此之外,还有链接到可能存在的各种其他环境的“环境分支”。

通常存在三个环境(可以根据需要创建更多环境):

  • 测试环境 == master 分支
  • 预生产环境 == preprod 分支
  • 生产环境 == prod 分支

进入其中一个环境分支的代码应立即移至相应的环境中,此流程可通过以下方式完成:

  • 自动(我们将在第 2 部分和第 3 部分探讨)
  • 半自动(与自动方式相同,唯一的区别是应按下按钮授权部署)
  • 手动

完整的流程是:

  1. 在功能分支中开发功能。
  2. 对功能分支进行审查并将其合并到 master 分支中。
  3. 一段时间(合并了多个功能)后,将 master 分支合并到 preprod 分支中
  4. 一段时间(用户测试等)后,将 preprod 分支合并到 prod 分支中
  5. 在我们进行合并和测试时,多个新功能已开发完毕并合并到 master 分支中,因此转到  3。

具体如下图所示:

有关 GitLab 流程的更多信息,请参阅此处

结论

  • Git是一个非线性的分布式版本控制系统。
  • Git 流程可用作软件开发周期的准则,有多种 Git 流程可供选择。

链接

讨论问题

  • 您使用 Git 流程吗? 使用哪一种?
  • 您为普通项目使用多少个环境?

后续内容

在接下来的部分中,我们将:

  • 安装 GitLab。
  • 探讨一些建议的调整。
  • 讨论 GitLab 工作流(不要与 GitLab 流程混淆)。

敬请关注。

0
0 129
文章 Michael Lei · 二月 18, 2024 11m read

1. IRIS RAG Demo

IRIS RAG Demo

这是 IRIS 与 RAG(检索增强生成)示例的一个简单演示。 后端是使用 IRIS 和 IoP用 Python 编写的,LLM 模型是 orca-mini 并由 ollama 服务器提供。 前端是用 Streamlit 编写的聊天机器人。

    1. IRIS RAG 演示](#1-iris-rag-demo)
    • 1.1. 什么是 RAG](#11-what-is-rag)
    • 1.2. 如何工作?
    • 1.3. 安装演示](#13-installation-the-demo)
    • 1.4. 使用方法
    • 1.5. 演示如何运行](#15-演示如何运行)
      • [1.5.1. 前端](#151-前端)
      • 1.5.2. 后台
        • [1.5.2.1. 业务服务](#1521-业务服务)
        • [1.5.2.2. 业务流程](#1522-业务流程)
        • [1.5.2.3. LLM 操作](#1523-the-llm-operation)
        • 1.5.2.4. 矢量操作](#1524-the-vector-operation)
    • 1.6. 一般性说明](#16-一般性说明)

1.1. 什么是 RAG?

RAG 是 Retrieval Augmented Generation(检索增强生成)的缩写,它带来了使用带有知识库的 LLM 模型(GPT-3.5/4、Mistral、Orca 等)的能力。

为什么它很重要? 因为它允许使用知识库来回答问题,并使用 LLM 来生成答案。

例如,如果你直接向 LLM 询问**"grongier.pex 模块是什么?"**,它将无法回答,因为它不知道这个模块是什么(也许你也不知道🤪)。

但是,如果你向 RAG 提出同样的问题,它就能回答,因为它会使用知识库,知道 grongier.pex 模块是什么,从而找到答案。

既然你已经知道什么是 RAG,那就让我们来看看它是如何工作的。

1.2. 它是如何工作的?

首先,我们需要了解 LLMS 的工作原理。LLMS 经过训练,可以根据前一个单词预测下一个单词。因此,如果你给它一个句子,它就会尝试预测下一个词,以此类推。很简单吧?

要与 LLM 交互,通常需要给它一个提示,它就会生成句子的其余部分。例如,如果你给它一个提示 "什么是 grongier.pex 模块?

很抱歉,我对您提到的 Pex 模块并不熟悉。能否请您提供有关它的更多信息或上下文?

好的,不出所料,它不知道什么是 grongier.pex 模块。但如果我们给它一个包含答案的提示呢?例如,如果我们提示``什么是 grongier.pex 模块?它是一个可以让你做 X、Y 和 Z 的模块。`",它就会生成剩下的句子,看起来就像这样:

grongier.pex 模块是一个可以让你执行 X、Y 和 Z 的模块。

好了,现在它知道什么是 grongier.pex 模块了。

但如果我们不知道 grongier.pex 模块是什么呢?我们怎样才能给它一个包含答案的提示呢? 这就需要知识库了。

RAG

RAG 的整个思路是使用知识库找到上下文,然后使用 LLM 生成答案。

为了找到上下文,RAG 将使用一个**知识库。

1.3.安装演示

只需克隆存储库并运行“docker-compose up”命令即可。

git clone https://github.com/grongierisc/iris-rag-demo
cd iris-rag-demo
docker-compose up

⚠️ 一切都是本地的,没有任何东西发送到云端,所以请耐心等待,可能需要几分钟才能开始。

1.4.用法

演示开始后,您可以在 http://localhost:8051 访问前端。

![Frontend](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/iris_chat.png?raw=true)

你可以提出有关「综合注册资讯系统」的问题,例如:

  • 什么是grongier.pex模块?

![Question](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/without_rag.png?raw=true)

正如你所看到的,答案不是很好,因为 LLM 不知道什么是 grongier.pex 模块。

现在,让我们尝试使用 RAG:

上传“grongier.pex”模块文档,它位于“docs”文件夹中,文件“grongier.pex.md”。

并问同样的问题:

  • 什么是grongier.pex模块?

![Question](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/with_rag.png?raw=true)

正如你所看到的,答案要好得多,因为 LLM 现在知道什么是 grongier.pex 模块。

您可以在日志中看到详细信息:

转到管理门户, http://localhost:53795/csp/irisapp/EnsPortal.ProductionConfig.zen?$NAMESPACE=IRISAPP&$NAMESPACE=IRISAPP&,然后单击“消息”选项卡。

首先,您将看到发送到 RAG 进程的消息:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_query.png?raw=true)

然后是知识库(向量数据库)中的搜索查询:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_result_vector.png?raw=true)

最后,发送给 LLM 的新提示:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_new_query.png?raw=true)

1.5.这个Demo如何工作?

该演示由 3 个部分组成:

  • 前端,用 Streamlit 编写
  • 后端,用 Python 和 IRIS 编写
  • 知识库 Chroma 向量数据库
  • LLM,Orca-mini,由 Ollama 服务器提供服务

1.5.1.前端

前端是用 Streamlit 编写的,它是一个简单的聊天机器人,可让您提出问题。

这里没什么花哨的,只是一个简单的聊天机器人。

import os
import tempfile
import time
import streamlit as st
from streamlit_chat import message

from grongier.pex import Director

_service = Director.create_python_business_service("ChatService")

st.set_page_config(page_title="ChatIRIS")


def display_messages():
    st.subheader("Chat")
    for i, (msg, is_user) in enumerate(st.session_state["messages"]):
        message(msg, is_user=is_user, key=str(i))


def process_input():
    if st.session_state["user_input"] and len(st.session_state["user_input"].strip()) > 0:
        user_text = st.session_state["user_input"].strip()
        with st.spinner(f"Thinking about {user_text}"):
            rag_enabled = False
            if len(st.session_state["file_uploader"]) > 0:
                rag_enabled = True
            time.sleep(1) # help the spinner to show up
            agent_text = _service.ask(user_text, rag_enabled)

        st.session_state["messages"].append((user_text, True))
        st.session_state["messages"].append((agent_text, False))


def read_and_save_file():

    for file in st.session_state["file_uploader"]:
        with tempfile.NamedTemporaryFile(delete=False,suffix=f".{file.name.split('.')[-1]}") as tf:
            tf.write(file.getbuffer())
            file_path = tf.name

        with st.spinner(f"Ingesting {file.name}"):
            _service.ingest(file_path)
        os.remove(file_path)

    if len(st.session_state["file_uploader"]) > 0:
        st.session_state["messages"].append(
            ("File(s) successfully ingested", False)
        )

    if len(st.session_state["file_uploader"]) == 0:
        _service.clear()
        st.session_state["messages"].append(
            ("Clearing all data", False)
        )

def page():
    if len(st.session_state) == 0:
        st.session_state["messages"] = []
        _service.clear()

    st.header("ChatIRIS")

    st.subheader("Upload a document")
    st.file_uploader(
        "Upload document",
        type=["pdf", "md", "txt"],
        key="file_uploader",
        on_change=read_and_save_file,
        label_visibility="collapsed",
        accept_multiple_files=True,
    )

    display_messages()
    st.text_input("Message", key="user_input", on_change=process_input)


if __name__ == "__main__":
    page()

💡 我只是在用 :

_service = Director.create_python_business_service("ChatService")

来创建一个前后端之间的绑定.

ChatService 只是互操作性生产中的简单业务服务BS。

1.5.2.后端

后端是用 Python 和 IRIS 编写的。

它由3个部分组成:

  • 业务服务BS
    • 前端的入口点
  • 业务流程BP
    • 如果需要,在知识库中执行搜索
  • 拖曳业务运营BO
    • 一个用于知识库
      • 摄取文档
      • 搜索文档
      • 清除文件
    • 一个用于LLM大模型
      • 生成答案

1.5.2.1.业务服务BS

业务服务是一个简单的业务服务,它允许:

  • 上传文件
  • 提出问题
  • 清除向量数据库
from grongier.pex import BusinessService

from rag.msg import ChatRequest, ChatClearRequest, FileIngestionRequest

class ChatService(BusinessService):

    def on_init(self):
        if not hasattr(self, "target_chat"):
            self.target_chat = "ChatProcess"
        if not hasattr(self, "target_vector"):
            self.target_vector = "VectorOperation"

    def ingest(self, file_path: str):
        # build message
        msg = FileIngestionRequest(file_path=file_path)
        # send message
        self.send_request_sync(self.target_vector, msg)

    def ask(self, query: str, rag: bool = False):
        # build message
        msg = ChatRequest(query=query)
        # send message
        response = self.send_request_sync(self.target_chat, msg)
        # return response
        return response.response

    def clear(self):
        # build message
        msg = ChatClearRequest()
        # send message
        self.send_request_sync(self.target_vector, msg)

基本上,它只是操作和过程之间的传递。

1.5.2.2.业务流程

业务流程是一个简单的过程,允许在需要时搜索知识库

from grongier.pex import BusinessProcess

from rag.msg import ChatRequest, ChatResponse, VectorSearchRequest

class ChatProcess(BusinessProcess):
    """
    the aim of this process is to generate a prompt from a query
    if the vector similarity search returns a document, then we use the document's content as the prompt
    if the vector similarity search returns nothing, then we use the query as the prompt
    """
    def on_init(self):
        if not hasattr(self, "target_vector"):
            self.target_vector = "VectorOperation"
        if not hasattr(self, "target_chat"):
            self.target_chat = "ChatOperation"

        # prompt template
        self.prompt_template = "Given the context: \n {context} \n Answer the question: {question}"


    def ask(self, request: ChatRequest):
        query = request.query
        prompt = ""
        # build message
        msg = VectorSearchRequest(query=query)
        # send message
        response = self.send_request_sync(self.target_vector, msg)
        # if we have a response, then use the first document's content as the prompt
        if response.docs:
            # add each document's content to the context
            context = "\n".join([doc['page_content'] for doc in response.docs])
            # build the prompt
            prompt = self.prompt_template.format(context=context, question=query)
        else:
            # use the query as the prompt
            prompt = query
        # build message
        msg = ChatRequest(query=prompt)
        # send message
        response = self.send_request_sync(self.target_chat, msg)
        # return response
        return response

这真的很简单,它只是向知识库发送一条消息来搜索文档。

如果 知识库 返回文档,则会使用文档内容作为提示,否则会使用查询作为提示。

1.5.2.3.LLM 操作

LLM 操作是一个简单的操作,可以生成答案。


class ChatOperation(BusinessOperation):

    def __init__(self):
        self.model = None

    def on_init(self):
        self.model = Ollama(base_url="http://ollama:11434",model="orca-mini")

    def ask(self, request: ChatRequest):
        return ChatResponse(response=self.model(request.query))

这一步也很简单,它只是向 LLM 发送一条消息来生成答案。

1.5.2.4.Vector 操作

向量操作是一个简单的操作,允许摄取文档、搜索文档和清除向量数据库。


class VectorOperation(BusinessOperation):

    def __init__(self):
        self.text_splitter = None
        self.vector_store = None

    def on_init(self):
        self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
        self.vector_store = Chroma(persist_directory="vector",embedding_function=FastEmbedEmbeddings())

    def ingest(self, request: FileIngestionRequest):
        file_path = request.file_path
        file_type = self._get_file_type(file_path)
        if file_type == "pdf":
            self._ingest_pdf(file_path)
        elif file_type == "markdown":
            self._ingest_markdown(file_path)
        elif file_type == "text":
            self._ingest_text(file_path)
        else:
            raise Exception(f"Unknown file type: {file_type}")

    def clear(self, request: ChatClearRequest):
        self.on_tear_down()

    def similar(self, request: VectorSearchRequest):
        # do a similarity search
        docs = self.vector_store.similarity_search(request.query)
        # return the response
        return VectorSearchResponse(docs=docs)

    def on_tear_down(self):
        docs = self.vector_store.get()
        for id in docs['ids']:
            self.vector_store.delete(id)
        
    def _get_file_type(self, file_path: str):
        if file_path.lower().endswith(".pdf"):
            return "pdf"
        elif file_path.lower().endswith(".md"):
            return "markdown"
        elif file_path.lower().endswith(".txt"):
            return "text"
        else:
            return "unknown"

    def _store_chunks(self, chunks):
        ids = [str(uuid.uuid5(uuid.NAMESPACE_DNS, doc.page_content)) for doc in chunks]
        unique_ids = list(set(ids))
        self.vector_store.add_documents(chunks, ids = unique_ids)
        
    def _ingest_text(self, file_path: str):
        docs = TextLoader(file_path).load()
        chunks = self.text_splitter.split_documents(docs)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)
        
    def _ingest_pdf(self, file_path: str):
        docs = PyPDFLoader(file_path=file_path).load()
        chunks = self.text_splitter.split_documents(docs)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)

    def _ingest_markdown(self, file_path: str):
        # Document loader
        docs = TextLoader(file_path).load()

        # MD splits
        headers_to_split_on = [
            ("#", "Header 1"),
            ("##", "Header 2"),
        ]

        markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
        md_header_splits = markdown_splitter.split_text(docs[0].page_content)

        # Split
        chunks = self.text_splitter.split_documents(md_header_splits)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)

如果文档太大,那么向量数据库将无法存储它们,因此我们需要将它们拆分为块。

如果文档是 PDF,那么我们将使用“PyPDFLoader”来加载 PDF,否则我们将使用“TextLoader”来加载文档。

然后,我们将使用“RecursiveCharacterTextSplitter”将文档拆分为块。

最后,我们将块存储到向量数据库中。

如果文档是 Markdown,那么我们将使用“MarkdownHeaderTextSplitter”将文档拆分为块。我们还使用标题将文档拆分为块。

1.6.A. 总 论

所有这些都可以通过“langchains”来完成,但我想向你展示如何使用互操作性框架来做到这一点。并让每个人都更容易理解它是如何工作的。

1
0 587
文章 Michael Lei · 九月 18, 2023 6m read

如今,关于大语言模型、人工智能等的消息不绝于耳。向量数据库是其中的一部分,并且已经有非IRIS的技术实现了向量数据库。

为什么是向量?

  • 相似性搜索:向量可以进行高效的相似性搜索,例如在数据集中查找最相似的项目或文档。传统的关系数据库是为精确匹配搜索而设计的,不适合图像或文本相似性搜索等任务。
  • 灵活性:向量表示形式用途广泛,可以从各种数据类型派生,例如文本(通过 Word2Vec、BERT 等嵌入)、图像(通过深度学习模型)等。
  • 跨模态搜索:向量可以跨不同数据模态进行搜索。例如,给定图像的向量表示,人们可以在多模式数据库中搜索相似的图像或相关文本。

还有许多其他原因。

因此,对于这次 pyhon 竞赛,我决定尝试实现这种支持。不幸的是我没能及时完成它,下面我将解释原因。

0
0 161
文章 Qiao Peng · 一月 14, 2021 12m read

你好,开发者!

你们中的许多人在 Open Exchange 和 Github 上发布了 InterSystems ObjectScript 库。

但对于开发者来说,如何简化项目的使用和协作呢?

在本文中,我想介绍一种简单方法,只需将一组标准文件复制到你的仓库中,就可以启动任何 ObjectScript 项目和对其做出贡献。

我们开始吧!

TLDR - 将以下文件从该仓库复制到你的仓库:

Dockerfile

docker-compose.yml

Installer.cls

iris.script

settings.json{#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

.dockerignore{#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}
.gitattributes{#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}
.gitignore{#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

你已经知道启动你的项目和协作的标准方式。 以下将详细说明这样做的步骤和原因。

**注意:**在本文中,我们将考虑可在 InterSystems IRIS 2019.1 及更新版本上运行的项目。

选择 InterSystems IRIS 项目的启动环境

通常,我们希望开发者尝试项目/库,并确保这是快速安全的练习。

在我看来,快速安全地启动任何新项目的理想方式是 Docker 容器,它可以保证开发者启动、导入、编译和计算任何内容对于主机来说都是安全的,并且不会破坏任何系统或代码。如果出了问题,只需停止并删除容器即可。 如果应用程序占用大量磁盘空间,使用容器将其擦除,空间就回来了。 如果某个应用程序破坏了数据库配置,只需删除配置被破坏的容器。 简单又安全。

Docker 容器提供安全和标准化。

运行通用 InterSystems IRIS Docker 容器的最简单方法是运行 IRIS 社区版映像

  1. 安装 Docker desktop 

  2. 在操作系统终端中运行以下命令:

docker run --rm -p 52773:52773 --init --name my-iris store/intersystems/iris-community:2020.1.0.199.0
  1. 然后在主机浏览器上打开管理门户:

http://localhost:52773/csp/sys/UtilHome.csp

  1. 或者打开终端启动 IRIS:

    docker exec -it my-iris iris session IRIS

  2. 不需要 IRIS 容器时,将其停止:

    docker stop my-iris

好! 我们在一个 docker 容器中运行 IRIS。 但是你希望开发者将你的代码安装到 IRIS 中,可能还要进行一些设置。 这就是我们下面要讨论的。

导入 ObjectScript 文件

最简单的 InterSystems ObjectScript 项目可以包含一组 ObjectScript 文件,例如类、例程、宏和global。 请查看有关命名和建议的文件夹结构的文章。

问题是如何将所有这些代码导入 IRIS 容器?

此时我们可以借助 Dockerfile 来获取通用 IRIS 容器,并将某个仓库中的所有代码导入 IRIS,然后根据需要对 IRIS 进行一些设置。 我们需要在仓库中添加一个 Dockerfile。

我们来看一下 ObjectScript 模板仓库中的 Dockerfile

ARG IMAGE=store/intersystems/irishealth:2019.3.0.308.0-community
ARG IMAGE=store/intersystems/iris-community:2019.3.0.309.0
ARG IMAGE=store/intersystems/iris-community:2019.4.0.379.0
ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0
FROM $IMAGE

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src
COPY iris.script /tmp/iris.script # run iris and initial 

RUN iris start IRIS \
    && iris session IRIS < /tmp/iris.script

 

前几个 ARG 行设置 $IMAGE 变量,随后在 FROM 中使用该变量。 这适合在不同的 IRIS 版本中测试/运行代码,只需在 FROM 前面的最后一行中更改 $IMAGE 变量即可切换版本。

这里我们采用:

ARG IMAGE=store/intersystems/iris-community:2020.1.0.199.0

FROM $IMAGE

这意味着我们将使用 IRIS 2020 社区版 build 199。

我们想要导入仓库中的代码,这意味着我们需要将仓库中的文件复制到 docker 容器。 下面几行完成此操作:

USER root

WORKDIR /opt/irisapp
RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp

USER irisowner

COPY  Installer.cls .
COPY  src src

USER root - 这里我们将用户切换为 root,以在 docker 中创建文件夹和复制文件。

WORKDIR  /opt/irisapp - 我们在此行中设置将文件复制到的工作目录。

RUN chown ${ISC_PACKAGE_MGRUSER}:${ISC_PACKAGE_IRISGROUP} /opt/irisapp   -  这里我们为运行 IRIS 的 irisowner 用户和组授予权限。

USER irisowner - 将用户从 root 切换到 irisowner

COPY Installer.cls .  -Installer.cls 复制到工作目录的根目录。 不要漏了那个点儿。

COPY src src - 将仓库的 src 文件夹中的源文件复制到 docker 上的工作目录的 src 文件夹中。

在下一个块中,我们运行初始脚本,其中将调用安装程序和 ObjectScript 代码:

COPY iris.script /tmp/iris.script # run iris and initial 
RUN iris start IRIS \
    && iris session IRIS < /tmp/iris.script

COPY iris.script / - 我们将 iris.script 复制到根目录。 它包含我们要调用以设置容器的 ObjectScript。

RUN iris start IRIS</span>  - 启动 IRIS

&& iris session IRIS < /tmp/iris.script - 启动 IRIS 终端并在其中输入初始 ObjectScript。

很好! 我们有 Dockerfile,它将文件导入 docker。 但还有两个文件:installer.cls 和 iris.script。我们来检查一下。

Installer.cls

Class App.Installer
{

XData setup
{
<Manifest>
  <Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>
  <Default Name="Namespace" Value="IRISAPP"/>
  <Default Name="app" Value="irisapp" />

  <Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    <Configuration>
      <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    </Configuration>
    <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  </Namespace>

</Manifest>
}

ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]
{
  #; Let XGL document generate code for this method. 
  Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "setup")
}

}

坦白说,我们不需要 Installer.cls 就可以导入文件。 这只需要一行就能完成。 但除了导入代码之外,我们经常还需要设置 CSP 应用,引入安全设置,创建数据库和命名空间。

在此 Installer.cls 中,我们创建了名为 IRISAPP 的新数据库和命名空间,并为该命名空间创建了默认的 /csp/irisapp 应用程序。

所有这些都在元素中 <Namespace> 执行:

<Namespace Name="${Namespace}" Code="${Namespace}" Data="${Namespace}" Create="yes" Ensemble="no">

    <Configuration>
      <Database Name="${Namespace}" Dir="/opt/${app}/data" Create="yes" Resource="%DB_${Namespace}"/>

      <Import File="${SourceDir}" Flags="ck" Recurse="1"/>
    </Configuration>
    <CSPApplication Url="/csp/${app}" Directory="${cspdir}${app}"  ServeFiles="1" Recurse="1" MatchRoles=":%DB_${Namespace}" AuthenticationMethods="32"
       
    />
  </Namespace>

我们用 Import 标签导入 SourceDir 中的所有文件:

<Import File="${SourceDir}" Flags="ck" Recurse="1"/>

这里的 SourceDir 是一个变量,设置为当前目录/src 文件夹:

<Default Name="SourceDir" Value="#{$system.Process.CurrentDirectory()}src"/>

有了带这些设置的 Installer.cls,我们可以自信地创建一个干净的新数据库 IRISAPP,我们将从 src 文件夹导入任意 ObjectScript 代码到该数据库中。

iris.script

欢迎在这里提供任何所需的初始 ObjectScript 设置代码来启动 IRIS 容器。

例如, 我们加载并运行 installer.cls,然后让 UserPasswords 永远有效,以避免第一次启动时出现密码更改请求,因为开发不需要这个提示。

; run installer to create namespace
do $SYSTEM.OBJ.Load("/opt/irisapp/Installer.cls", "ck")
set sc = ##class(App.Installer).setup()  zn "%SYS"
Do ##class(Security.Users).UnExpireUserPasswords("*") ; call your initial methods here
halt

docker-compose.yml

为什么需要 docker-compose.yml,不能只用 Dockerfile 构建和运行映像吗? 可以的。 但 docker-compose.yml 能让生活更轻松。

通常,docker-compose.yml 用于启动连接到一个网络的多个 docker 映像。

当启动一个 docker 映像需要处理多个参数时,也可以使用 docker-compose.yml 来简化过程。 你可以使用它向 docker 传递参数,例如端口映射、卷、VSCode 连接参数。

version: '3.6' 
services:
  iris:
    build: 
      context: .
      dockerfile: Dockerfile
    restart: always
    ports: 
      - 51773
      - 52773
      - 53773
    volumes:
      - ~/iris.key:/usr/irissys/mgr/iris.key
      - ./:/irisdev/app

这里我们声明服务 iris,它使用 docker 文件 Dockerfile,并开放 IRIS 的以下端口:51773、52773、53773。 此服务还映射两个卷:将主机主目录中的 iris.key 映射到期望的 IRIS 文件夹,以及将源代码的根文件夹映射到 /irisdev/app 文件夹。

Docker-compose 提供了更短的统一命令来构建和运行映像,无论你在 docker compose 中设置了什么参数。

总之,构建和启动镜像的命令是:

$ docker-compose up -d

要打开 IRIS 终端:

$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>

此外,docker-compose.yml 有助于设置 VSCode ObjectScript 插件的连接。

.vscode/settings.json

与 ObjectScript 加载项连接设置有关的部分如下:

{
    "objectscript.conn" :{
      "ns": "IRISAPP",
      "active": true,
      "docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }
    }     

}

在这里,我们看到的设置与 VSCode ObjectScript 插件的默认设置不同。

我们想要连接到 IRISAPP 命名空间(我们用 Installer.cls 创建的):

"ns": "IRISAPP",

一个 docker-compose 设置指示,docker-compose 文件在服务“iris”内,VSCode 将连接到 52773 映射到的端口:

"docker-compose": {
        "service": "iris",
        "internalPort": 52773
      }

如果我们检查一下 52773 的相关设置,我们会看到没有为 52773 定义映射端口:

ports: 
      - 51773
      - 52773
      - 53773

这意味着将使用主机上的随机可用端口,并且 VSCode 将通过随机端口自动连接到 docker 上的 IRIS。

这是一个非常方便的功能,因为它允许在随机端口上运行任意数量的带 IRIS 的 docker 映像,并让 VSCode 自动连接到它们。

其他文件呢?

我们还有:

.dockerignore  - 该文件可用于过滤你不想复制到所构建的 docker 映像中的主机文件。 通常 .git 和 .DS_Store 是必须有的行。

.gitattributes - git 的属性,用于统一来源中的 ObjectScript 文件的行尾。 如果仓库由 Windows 和 Mac/Ubuntu 所有者协作,此文件非常有用。

.gitignore - 你不希望 git 跟踪其更改历史记录的文件。 通常是一些隐藏的操作系统级文件,例如 .DS_Store。

好了!

如何使你的仓库可被 docker 运行并且对协作友好?

  1. 克隆此仓库

  2. 复制以下所有文件:

Dockerfile

docker-compose.yml

Installer.cls

iris.script

settings.json{#9f423fcac90bf80939d78b509e9c2dd2-d165a4a3719c56158cd42a4899e791c99338ce73}

.dockerignore{#f7c5b4068637e2def526f9bbc7200c4e-c292b730421792d809e51f096c25eb859f53b637}
.gitattributes{#fc723d30b02a4cca7a534518111c1a66-051218936162e5338d54836895e0b651e57973e1}
.gitignore{#a084b794bc0759e7a6b77810e01874f2-e6aff5167df2097c253736b40468e7b21e577eeb}

到你的仓库。

更改 Dockerfile 中的这一行,使目录与仓库中要导入 IRIS 的 ObjectScript 匹配(如果在 /src 文件夹中则不要更改)。

就这样。 每个人(也包括你)都会将你的代码导入到新的 IRISAPP 命名空间的 IRIS 中。

人们如何启动你的项目

在 IRIS 中执行任何 ObjectScript 项目的法则为:

  1. Git clone 项目到本地

  2. 运行项目:

$ docker-compose up -d
$ docker-compose exec iris iris session iris

Node: 05a09e256d6b, Instance: IRIS

USER>zn "IRISAPP"

开发者如何为你的项目做出贡献

  1. 对仓库执行分叉,并将分叉后的仓库 git clone 到本地

  2. 在 VSCode 中打开该文件夹(还需要在 VSCode 中安装 DockerObjectScript 扩展)

  3. 右击 docker-compose.yml->重启 - VSCode ObjectScript 将自动连接并准备好编辑/编译/调试

  4. 向你的仓库提交、推送和拉取请求更改

以下是操作过程的短 gif:

好了! 编码愉快!

0
0 250