Skip to main content

4 posts tagged with "QPod"

View All Tags

🀄️ 基于容器化的开发和DevOps的实践和经验

· 12 min read
Bibo Hao
Founder and Maintainer of QPod

本文简述我们在进行开发和DevOps工作中使用容器化的实践、经验和理念。

A. 我们对容器使用的理念

容器技术在软件开发和应用部署等领域广泛应用以来,在下面几方面相较于传统的开发和部署模式带来了新的改变。

A1. 提供一致的环境

无论开发者是在本地的开发环境,如Linux(ubuntu/centos)、Windows(WSL)、macOS(Intel/M系列芯片)等平台上,还是在构建环境(如CI/CD流水线)、还是运行环境(如生产环境), 都可以使用容器,确保使用相同的环境,消除各种因为环境、版本、配置等的差异,带来的协调困难,减少“在我机器上没问题”的情况。

A2. 降低复杂环境管理和配置难度

通过容器镜像的统一管理、团队在各种环境均使用相同的镜像,可以大幅减少每位开发者花费大量时间,在自己的环境中安装软件、进行配置的时间。

一些常见的痛点包括:

  • 对AI和算法类工程师:在不同的环境中安装配置NVIDIA cuda系列套件,并维护cuda版本的更新,十分花费时间,尤其是在企业局域网内网环境;
  • 对前端开发者:需要处理NodeJS,以及npm/yarn/pnpm自身版本的管理和更新问题,每次版本的更新均需要花费一定的时间来更新配置;
  • 对后端开发者:同样对于Golang/Java/Python/Rust/C++等环境的程序包安装、配置、更新,需要花费较大精力进行更新、维护和管理;
  • 对数据科学家:在使用Python/R等数据科学工具包时,跨平台版工具包版本的管十分头疼,例如:Windows/Linux/macOS上不同版本的pytorch等包,有一些包需要进行C++编译才能安装,还有一些包通过rJava/py4j调用Java程序时候又涉及JRE/JDK的管理。

而如果通过维护一套统一的容器镜像栈,就可以消除这些困难。当然,对这些容器镜像的管理,也需要通过“代码定义的配置(Code defined configuration)”来确保这些镜像的可维护性。具体来说:

  • 需要使用Dockerfile而不是通过docker commit来管理镜像,保证镜像构建的可重复性和可维护性;
  • 建议把Dockerfile中进行的操作抽象提取到shell脚本中,用shell脚本函数来定义大部分操作,而在Dockerfile中只执行函数的调用,这样的脚本更易于维护、易读(相较于Dockerfile)、并且可以在其他平台环境执行。

经过多年的迭代,我们不断打磨我们的实践,形成了QPod Stack系列镜像,其源代码也完全开源:https://github.com/QPod/lab-foundation 。这一些列的镜像是我们的理念的实践,并且使用GitHub Actions(这一免费的资源)进行构建和管理。

当然您也完全可以构建自己的镜像栈,维护自己的软件供应链体系。

这一实践带来的好处显而易见:

  1. 当开发者需要某一个系列的开发环境时(例如Golang,或者包含了pytorch的环境),那么只要选取一个镜像,docker pull到自己的环境就可以进行开发,只需要把源代码通过docker volume mount到启动的容器中即可。
  2. 当开发者需要更新环境中的软件或package(例如NVIDIA cuda)版本时,只需要拉取最新版本(latest)或指定版本的镜像即可,无需从主机上下载、安装、配置环境。
  3. 将镜像、环境的管理交由专人(或者可信任的开源社区)管理、维护、开发,一方面可以确保代码定义的环境的可追溯管理和软件供应链安全,另一方面这在在该领域有专长的开发者更能够进行合理的安装和配置(例如:当你在你的笔记本上运行pytorch程序时,如果配置不当则会安装GPU版本的pytorch,无端占用更多存储空间)。

A3. 简化部署和运维

对开发人员来说,在开发测试环境的环境搭建工作中,希望尽可能简单的操作,例如:

  • 快速搭建启动一个数据库;
  • 快速启动一个中间件(例如Nginx)而且只需要关心其中最核心的配置部分(例如Nginx的server和location部分)。

对运维人员来说:在生产环境的运维工作,希望有尽可能简单的操作,例如通过docker-compose文件或helm chart等,来实现:

  • 拉取最新版本的artifact(构建,例如docker images);
  • 不必关心各种软件包的安装、更新等管理、不必关心环境变量等的配置;
  • 仅仅通过一两个简单的命令操作实现服务的停止、启动、重启;
  • 将生产环境密码、secrets、certificates等独立管理,不让未授权的人员访问(如开发人员)。

这些需求和痛点都可以通过编写不同环境的docker-compose文件或helm chart来实现。

A5. 其他的好处

  • 资源利用率提升:与传统虚拟机相比,容器更轻量级,能够在单一主机上运行更多实例,有效利用计算资源。
  • 应用隔离与安全:容器为每个应用提供隔离环境,降低了应用之间的相互影响风险,同时通过镜像控制与权限管理增强了安全性。

B. 一些具体的实践细节

B1. 统一镜像栈的代码管理和CI/CD

我们的源代码完全公开在GitHub上:https://github.com/QPod/lab-foundation

镜像的构建完全通过(免费的)GitHub Actions来构建:https://github.com/QPod/lab-foundation/blob/main/.github/workflows/build-docker.yml

我们也针对几个不同的开发领域构建几个代码库来分别管理各个系列的镜像栈:

  • lab-foundation:维护了一些列基础镜像,例如各前后端编程语言环境,以及NVIDIA cuda和torch等基础环境;
  • lab-data:维护了一些列常用数据库(Postgresql及附加插件)、大数据处理组件(spark/flink)等;
  • lab-dev:维护了一些列开发和线上应用中常用的组件,例如:nginx、网页内IDE(如VSCode和Jupyter)等;
  • lab-media:维护了一些列多媒体/多模态内容处理的组件,如OpenCV、PaddleOCR、HuggingFace Model等。

现有代码中,对各种软件包的安装,都会默认选取最新的release/stable版本,因此当有软件更新后,只需重新执行一下GitHub Actions,版本最新版本软件包的镜像就会被构建并推送到镜像库当中。

B2. 不同运行环境下的差异化处理

镜像会在不同的环境下使用,例如:不同的时区、不同的数据中心(例如北美、中国)、不同云服务提供商(例如Azure、AWS、aliyun)。 为了更好的在不同环境下使用这些镜像,仍需要在容器启动后,执行特定的个性化配置。

为此,我们定义了一系列脚本,用于处理在不同环境时需要执行的个性化配置操作。 例如,您的镜像运行在中国大陆地区的互联网上(或者你的个人电脑连接了中国大陆的互联网),这个脚本执行的个性化的配置将会:

B3. 在开发环境进行代码开发时的一些实践

如A2小节中提及的,开发者在自己的Windows(WSL)、macOS、Linux环境像,使用这些基础环境进行开发时候,可以:

  • 通过docker run来启动一个镜像,启动时用-v来把源代码的目录挂载到容器内,然后docker exec -it进入容器中,在容器内启动程序(例如:python main.pynpm run devgo run等)。

  • 编写一个docker-compose文件来实现上述逻辑,其中容器启动的command设置为:tail -f /dev/null,这样容器就会保持启动而不会运行即逝,然后再通过docker exec -it进入容器启动代码。

  • 如果您希望在网页IDE(包括VSCode、JupyterLab、RStudio)中进行开发,还可以使用我们维护的这个容器来进行开发:https://github.com/QPod/lab-dev/tree/main/docker_devbox

C. 总结

经过多年的开发、DevOps、运维实践,我们通过实践积累和打磨了上面的开源项目,形成了自己的实践来提升开发运维效率。

希望这些构建好的镜像、用于构建的源代码、我们的开发实践能对您和您的团队的开发有帮助,如果您有更多的需求想要交流、或者想要对这一项目进行贡献,也欢迎您到对应的GitHub repo当中提PR/issue。

📝 QPod CI/CD Toolkits and Practices

· 3 min read
Bibo Hao
Founder and Maintainer of QPod

In this brief blog, we will introduct the CI/CD philosophy, and our practices.

Our CI/CD Philosophy

Back to years ago, our team has tried CI/CD tools including but not limited to travis, GitLab runners, etc. Until recently, we adopted GitHub actions as our current choice.

As these tools evolves (or your choice changes), the code CI/CD pipelines, in the form of YAML files or manual configure pipelines, has to be refactored to fit new tools.

Our philosophy is to keep the CI/CD as simple as possible, and de-couple with the CI/CD tools. As such, it's a nature choice to put more function implementations in scripts/modules in source code, instead of relying on tools provided functionalities (e.g.: Github Actions).

Based on this philosophy, we established our CI/CD practice along with correspond toolkits.

The toolkits and the practices

Here is an example of our toolkits for CI/CD, which include the following parts:

  • tool.sh: a shell script which include several functions to build code or project.

  • github workflow YAML: a CI/CD platform specific configuration which use the tool.sh above, and use simple linux shell commands to finish most build tasks. As you can see, each job starts with source ./tool.sh to use the functions defined in our toolkits.

Other Choices

As built artifacts changes in developing / testing (stagging) / release (production) stages, we found it's important to manage the artificats in two perspective:

  1. Each artifact should be properly versioned and tagged, especially when a testing (stagging) or production environment needs a rollback.

  2. The release artifact repo (such as Docker registry) should be a seperated one with the developing / testing (stagging) one(s), in order to better manage the artifiacts for production environment, and use proper resources. In many cases, the production environment requires an aritifact repo that is high-available, and better to be in the same VPC/Zone/Region with the computing resources, or with different push/pull permissions.

As such, it became our choice to put artifacts of developing/testing stage and release/production artifiacts into different repo, at least differnt registry namespaces. In the tool.sh file above, you can see we use a variable DOCKER_IMG_NAMESPACE to decide which name space the artifact should be pushed into. Furthermore, this variable is decided by the Git branch name prefix:

if [ "${CI_PROJECT_BRANCH}" = "main" ] ; then
# If on the main branch, docker images namespace will be same as CI_PROJECT_NAME's name space
export CI_PROJECT_NAMESPACE="$(dirname ${CI_PROJECT_NAME})" ;
else
# not main branch, docker namespace = {CI_PROJECT_NAME's name space} + "0" + {1st substr before / in CI_PROJECT_SPACE}
export CI_PROJECT_NAMESPACE="$(dirname ${CI_PROJECT_NAME})0${CI_PROJECT_SPACE}" ;
fi

In this way:

  • when code is pushed or merged to main branch, artifiact repo namespace will be qpod in our example.
  • when code is pushed to a branch name in the pattern of dev/* and a PR is created to merge this branch dev/* into main, the artifact will be pushed to the namespace qpod0dev (the suffix 0dev can be customized).
  • when code is pushed to a branch without a PR into main, the code will not be built (pipeline will not be triggerd).

🀄️ QPod Stack的CI/CD的思路、实践、理念

· 5 min read
Bibo Hao
Founder and Maintainer of QPod

本文简单介绍了我们进行CI/CD的思路、实践、理念。

我们的 CI/CD 理念

过去几年中,我们的团队尝试了包括但不限于 Travis、GitLab 运行器等 CI/CD 工具。直到最近,我们选择了 GitHub Actions 作为当前的选择。

随着这些工具的发展(或团队选择的变化),代码的 CI/CD 流水线,无论是以 YAML 文件的形式还是手动配置的流水线,都需要重构以适应新工具。

我们的理念是尽可能保持 CI/CD 的简单性,并与 CI/CD 工具解耦。

因此,很自然的选择是 将更多的功能实现放在源代码中的脚本/模块里,而不是依赖于工具提供的功能(例如:GitHub Actions)。

基于这一理念,我们建立了我们的 CI/CD 实践以及相应的工具包。

以下是我们 CI/CD 的工具包示例,包括以下部分:

  • tool.sh:一个包含几个用于构建代码或项目的函数的 shell 脚本。

  • github workflow YAML:一个特定于 CI/CD 平台的配置,使用上述的 tool.sh,并使用简单的 Linux shell 命令完成大部分构建任务。如你所见,每个作业都以 source ./tool.sh 开始,以使用我们工具包中定义的函数。

其他选择

随着构建工件在开发/测试(暂存)/发布(生产)阶段的变化,我们发现从两个角度管理工件(artificats)很重要:

  1. 每个工件都应该正确地进行版本控制和标记,特别是在测试(暂存)或生产环境需要回滚时。

  2. 用于发布(生产环境)的artifact仓库(例如 Docker registry)应与开发/测试(暂存)仓库分开,以便更好地管理生产环境的工件,并使用适当的资源。在许多情况下,生产环境需要一个高可用的工件仓库,并且最好与计算资源位于同一 VPC/区域,或者具有不同的推送/拉取权限。

因此,我们选择将开发/测试阶段的工件和发布/生产工件放入不同的仓库,至少是不同的registry命名空间。在上面的 tool.sh 文件中,你可以看到我们使用一个变量 DOCKER_IMG_NAMESPACE 来决定工件应该被推送到哪个命名空间。此外,这个变量由 Git 分支名称前缀 决定:

if [ "${CI_PROJECT_BRANCH}" = "main" ] ; then
# 如果在主分支上,Docker 镜像命名空间将与 CI_PROJECT_NAME 的命名空间相同
export CI_PROJECT_NAMESPACE="$(dirname ${CI_PROJECT_NAME})" ;
else
# 不是主分支,Docker 命名空间设置为 {CI_PROJECT_NAME 的命名空间} + "0" + {CI_PROJECT_SPACE 中 / 之前的第一段}
export CI_PROJECT_NAMESPACE="$(dirname ${CI_PROJECT_NAME})0${CI_PROJECT_SPACE}" ;
fi

通过这种方式:

  • 当代码被推送或合并到 main 分支时,工件仓库命名空间将是我们示例中的 qpod

  • 当代码被推送到分支名称为 dev/* 的模式,并且创建了一个 PR 将这个分支 dev/* 合并到 main,工件将被推送到命名空间 qpod0dev(后缀 0dev 可以自定义)。

  • 当代码被推送到没有 PR 合并到 main 的分支时,代码将不会被构建(流水线不会被触发执行)。

👏 Welcome | 欢迎

· One min read
Bibo Hao
Founder and Maintainer of QPod

Welcome to QPod!


We aim at providing a set of foundation building blocks helping AI and Software Engineers for their AI/Data and development work.

Please visit our GitHub Repo and read the README files in each sub-folder as we are updating the docs.


我们的目标是提供一套基础构建模块,帮助人工智能和软件工程师进行他们的人工智能/数据和开发工作。

请访问我们的 GitHub 仓库,并阅读每个子文件夹中的 README 文件,因为我们正在更新文档。